Day 63:CSS 裝飾線實作與 Margin Collapse 父子穿透問題

📅 2026-02-25 | 🏷 CSS · Pseudo-elements · Box Model · Margin Collapse


一、畫水平裝飾線的兩種方式

電商網站的 Products 區常見標題下方的短裝飾線,實作上有兩個選項:

方式

語意

跨瀏覽器一致性

適用場景

<hr> 元素

代表段落主題轉換

預設樣式不一致,需 reset

內容層次上有分隔意義

CSS 偽元素

純裝飾

完全由 CSS 掌控

純視覺的短裝飾線

結論:如果裝飾線只是視覺點綴,不代表任何內容分隔語意,偽元素是更乾淨的選擇,符合「結構歸 HTML、裝飾歸 CSS」的分工原則。 developer.mozilla


二、::before / ::after 的佔位概念

::before::after 插入的位置,是在目標元素的內容裡面的前後,不是 HTML 結構中兄弟元素的順序。 developer.mozilla

┌─────────────────────────────┐
│  .element                   │
│  ┌──────┐ ┌───────┐ ┌─────┐│
│  │before│ │ 內容  │ │after││
│  └──────┘ └───────┘ └─────┘│
└─────────────────────────────┘

MDN 的定義:::before 是目標元素的「第一個子節點」,::after 是「最後一個子節點」,預設都是 display: inlinedeveloper.mozilla

⚠️ 最常見的新手陷阱

偽元素預設 display: inline,此時 widthheight 設定不生效。必須加上 display: blockdisplay: inline-block 才能控制尺寸。 bennadel

看不見,因為 inline 元素忽略 width/height */ .element::before { content: ''; width: 40px; height: 1px; background-color: #999; } /* ✅ 加上 display: block 才能設定尺寸 */ .element::before { content: ''; display: block; /* 關鍵 */ width: 40px; height: 1px; background-color: #999; }


三、裝飾線用 background-color vs border

兩種方式都能畫出 1px 的線,但設計意圖不同: developer.mozilla

考量點

background-color

border

語意

描述盒子的填色

描述盒子的邊線

圓角支援

可用 border-radius

線段本身不支援

box-sizing 影響

不受影響

影響總高度計算

適用場景

獨立裝飾線

元素邊緣框線(card、input 等)

結論:偽元素裝飾線用 background-color 更直觀。width + height + background-color 直接描述形狀與顏色,語意清楚,也不受 box-sizing 影響。

完整實作範例

/* Products 標題上方的短裝飾線 */
.products__subtitle::before {
  content: '';
  display: block;
  width: 40px;
  height: 1px;
  background-color: #999;
  margin: 0 auto 16px; /* 水平置中,下方推開 16px */
}

四、偽元素 Debug 技巧

開發時無法直接用 HTML 確認偽元素位置,可用以下方式:

  1. DevTools Elements 面板:展開元素節點,::before / ::after 會列在子節點裡

  2. 加背景色 debug:開發中先設 background-color: red 確認位置與尺寸,完成後換回正式顏色

  3. DevTools Computed 面板:查看偽元素的 box model 計算結果


五、偽元素撐高父層對 vertical-align 的影響

::before 設為 display: block 後,會成為父元素內部的區塊子元素,進而撐高父元素的內容盒。 developer.mozilla

  • 父元素為 display: block:正常行為,頁面整體對齊不受影響

  • 父元素為 display: inline-block::before 撐高元素後,vertical-align 的基線計算會偏移,造成同行兄弟元素錯位,需特別留意

沒有 ::before 時:
┌──────────────────────┐
│  inline-block 元素   │  基線在文字底部
└──────────────────────┘

有 ::before (display: block) 後:
┌──────────────────────┐
│ ┌──────────────────┐ │
│ │  ::before(高)  │ │  ← 這個高度把基線往下推
│ └──────────────────┘ │
│  文字                │  基線現在在這裡
└──────────────────────┘


六、Margin Collapse:父子元素的外距穿透

問題現象

Footer 的兩個子容器之間出現非預期間隔,但 DevTools 確認兩個容器的 margin 都是 0。

根本原因

Margin Collapse(外距折疊) 不只發生在相鄰兄弟元素之間,也會發生在父子元素之間developer.mozilla

當父層同時滿足以下條件,子元素的 margin 會穿透父層折疊到容器外部:

  • 沒有 border

  • 沒有 padding

  • 沒有 inline 內容(如文字)

<p> 的 margin-top 穿透 .footer__bottom
┌──────────────────┐       向上折疊,視覺上變成容器間的空隙
│ .footer__content │
└──────────────────┘
  ↑ 非預期的間距(其實是 <p> 的 margin)
┌──────────────────┐
│ .footer__bottom  │  ← 沒有 padding,無法阻擋
│  ┌────────────┐  │
│  │    <p>     │  │  ← 有預設 margin-top: 1em
│  └────────────┘  │
└──────────────────┘

驗證方式:DevTools → 點選 <p> 元素 → Computed → 確認 margin-top 有值。 developer.mozilla

修正方案與語意設計

padding 應設定在父層容器,而非子層 <p>

/* ✅ 容器主動負責內部留白 */
.footer__bottom {
  text-align: center;
  padding: 60px 0; /* padding 同時阻斷了 margin collapse */
}

.footer__copyright {
  color: rgb(200, 200, 200);
  margin: 0; /* 消除 <p> 預設 margin,語意更清晰 */
}

寫法

語意

padding 設在父層

容器主動把內容往內推擠,職責清晰

padding 設在子層

子元素自己撐大,責任邊界模糊

這個修法有「一石二鳥」的效果:父層獲得 padding 後,子層的 margin 就無法再穿透折疊出去,Margin Collapse 問題同時消失。 thecodingfox

記憶口訣

父層沒有 padding / border,子層的 margin 會「穿牆而出」。


七、防止 Margin Collapse 的所有方式

方式

說明

paddingborder

最常用,也最語意化

overflow: hidden

建立新的 BFC,阻斷折疊

display: flow-root

明確建立 BFC,無副作用

父層有 inline 內容(如文字)

自動阻斷折疊

robcallaghan.co


延伸閱讀


TODO

  • [ ] Add implementation examples

  • [ ] Add resource links

  • [ ] Proofread and format