margin系列之外邊距摺疊

doyoe發表於2013-12-04

原文地址:margin系列之外邊距摺疊 by @doyoe

不懷疑你也在工作中遇見過

幾乎可以不用懷疑,前端工作中的你一定遇見過 margin 摺疊。

不確定?彆著急,你可能寫過這樣的程式碼:

CSS:

p{
    margin: 50px;
}

HTML:

<div id="demo">
    <p>我是一個華麗的段落,別看我文字少</p>
    <p>我是另一個華麗的段落</p>
</div>

大家覺得這 2p 之間會發生點什麼?是會合體呢?還是分開?來看看 DEMO1 margin摺疊

好吧,它們真的合體了。按照常規思路,這 2p 之間的空白應該是第一個 pmargin-bottom 50px 加上第二 pmargin-top 50px,即 50 + 50px = 100px ,但結果總是出乎意料不是麼?它們之間只剩下了 50px,這就是 margin摺疊。所以任何人遇見過我都不會覺得意外,因為這樣的Code看起來沒有任何問題。

它們之間到底發生了些什麼?

2p 內部到底發生了什麼,才會有這樣的表現?

早在CSS1中就有對 margin 摺疊的說明,我們來看看:

原文:The width of the margin on non-floating block-level elements specifies the minimum distance to the edges of surrounding boxes. Two or more adjoining vertical margins (i.e., with no border, padding or content between them) are collapsed to use the maximum of the margin values. In most cases, after collapsing the vertical margins the result is visually more pleasing and closer to what the designer expects.

翻譯:外邊距用來指定非浮動元素與其周圍盒子邊緣的最小距離。兩個或兩個以上的相鄰的垂直外邊距會被摺疊並使用它們之間最大的那個外邊距值。多數情況下,摺疊垂直外邊距可以在視覺上顯得更美觀,也更貼近設計師的預期。

從這段話中,我們能獲得一些有用的資訊:

  • 發生摺疊需要是相鄰的非浮動元素;
  • 摺疊發生在垂直外邊距上,即margin-top/margin-bottom;
  • 摺疊後取其中最大的那個margin值作為最終值;

所以 DEMO1 中的 2p 符合摺疊的條件,相鄰且都不是浮動元素,於是它們就自然合體了。至於取最大的那個值作為摺疊後的最終值,因為都是50px,所以無所謂誰誰誰了。

為什麼會有margin摺疊這樣的設計?

從上文中,應該能知道個大概?在前面的文章中,我們說過,CSS的基本模型是排版。只是前端工程師現在做得更多的是 佈局 而非 文字排版,但CSS並未界定這兩者的區別。而 margin 摺疊是為實現文字排版的段落間距而提供的特性。所以很多時候 margin 摺疊的特性會帶給我們諸多疑惑。

再回到 DEMO1 仔細看看,你會驚訝的發現,其實你只要設定每個 p 有相同的垂直外邊距,由於發生會 margin 摺疊,所有的 p 之間,及包含塊與 p 之間的間隙都是相同的,非常美妙且實現簡單,不是麼?這正印證了我們引用 CSS1 中的那短話:多數情況下,摺疊垂直外邊距可以在視覺上顯得更美觀,也更貼近設計師的預期。

浮動元素真的不會發生margin摺疊嗎?

質疑精神一直都是進步最重要的驅動力之一,我們為什麼不能呢?改下程式碼看看:

CSS:

p{
    float: left;
    margin: 50px;
}

只改CSS程式碼,HTML不變。迫不及待的想看到驗證情況,來吧,還等什麼。DEMO2 驗證浮動元素是否會發生margin摺疊

結果告訴我們,真的沒有摺疊,2p 間的間隙足足有 100px

剩下的2點大家就自行驗證吧,相信你能得到滿意的額測試結果。

僅此而已?

回想一下我們在 margin系列之百分比 文中提到過受書寫模式影響的一些特性,非常不幸,margin 摺疊正好是其中之一。

是的,在CSS2及後續的規範中,將margin 摺疊描述得更為詳盡了。

在水平書寫模式下,發生 margin 摺疊的是垂直方向,即 margin-top/margin-bottom,在垂直書寫模式下,margin 摺疊發生在水平方向上,即 margin-right/margin-left

現在我們來解釋一下到底什麼是margin摺疊?

在CSS中,兩個或以上的塊元素(可能是兄弟,也可能不是)之間的相鄰外邊距可以被合併成一個單獨的外邊距。通過此方式合併的外邊距被稱為摺疊,且產生的已合併的外邊距被稱為摺疊外邊距。

處於同一個塊級上下文中的塊元素,沒有行框、沒有間隙、沒有內邊距和邊框隔開它們,這樣的元素垂直邊緣毗鄰,則稱之為相鄰。

什麼是垂直邊緣毗鄰?

  • 元素的上外邊距和其屬於常規流中的第一個孩子的上外邊距。
  • 元素的下外邊距和其屬於常規流中的下一個兄弟的上外邊距。
  • 屬於常規流中的最後一個孩子的下外邊距和其父親的下外邊距,如果其父親的高度計算值為 auto
  • 元素的上、下外邊距,如果該元素沒有建立新的塊級格式上下文,且 min-height 的計算值為零、height 的計算值為零或 auto、且沒有屬於常規流中的孩子。

說得很清楚了,我想是的。你可能需要注意的是發生 margin 摺疊的元素不一定是兄弟關係,也能是父子或祖先的關係。

如何避免margin摺疊?

我想肯定有人要問,那我不想有 margin 摺疊的情況發生,該怎麼辦?其實從上面的規則中,我們已經可以抽出避免 margin 摺疊的條件來。

  • margin 摺疊元素只發生在塊元素上;
  • 浮動元素不與其他元素 margin 摺疊;
  • 定義了屬性overflow且值不為visible(即建立了新的塊級格式化上下文)的塊元素,不與它的子元素髮生margin 摺疊;
  • 絕對定位元素的 margin 不與任何 margin 發生摺疊。
  • 特殊:根元素的 margin 不與其它任何 margin 發生摺疊;
  • 如果常規流中的一個塊元素沒有 border-toppadding-top,且其第一個浮動的塊級子元素沒有間隙,則該元素的上外邊距會與其常規流中的第一個塊級子元素的上外邊距摺疊。 可能有些繞,我們驗證一下 DEMO3,在其第一個浮動子元素加個全形空格做間隙,來個反證 DEMO4
  • 如果一個元素的 min-height 屬性為0,且沒有上或下邊框以及上或下內邊距,且 height 為0或者 auto,且不包含行框,且其屬於常規流的所有孩子的外邊距都摺疊了,則摺疊其外邊距。驗證一下 DEMO5

這樣幹掉margin摺疊

如果不想發生 margin 摺疊,那麼你可以根據上面的規則得到方法,不是麼?我把它改成非塊元素,讓它浮動,讓它絕對定位,讓它 overflow:hidden DEMO6,用邊框隔開它們 DEMO7...隨你怎樣,信手拈來。

今天狀態不太好,有些地方寫得欠妥,之後可能會修改一下。

BTW: 這篇文章裡可能有不少之前文章中沒出現過的名詞,比如:塊級上下文、行框、常規流,如果你對此不甚瞭解,可以先找資料看看,我以後會講到。

enjoy it.

可參考:

margin系列文章:

相關文章