建議在PC端閱讀
本文面向對象:對標題中的概念基本不了解或僅僅聽說過名字的人。如果已有一定了解請直接拉到最下看推薦閱讀
另外其實這是個很大的話題,這里僅當拋磚引玉,并提供進一步閱讀的文章
一些demo
我們先從一些現象開始入手吧↓(建議在另一窗口打開示例,一邊修改示例一邊閱讀效果更佳)
本文示例
例一
第一個例子是最常見的一種邊界折疊(Margin Collapse)。一般我們很容易忽略掉這個現象,因為它和我們大多數情況下的直覺比較符合。
例如第一個例子里的兩個p標簽,它們的關系是并列的,所以我們設置margin:20px 0;
時心里其實是想讓兩個p標簽之間相隔20px(事實上現在也是這樣表現的)。
但有時候我們并不想讓它們折疊(我們希望“修正”的同時,保持使用margin并盡量少添加難以理解的CSS屬性),那要怎么做呢?(即需要清楚產生邊界折疊的條件)
例子中提示的做法是用一個設置了
overflow:hidden;
的div將其包裹,但是overflow:hidden;
作用明明是剪裁溢出的內容,為何能產生取消邊界折疊的效果呢?直接給出這樣的解決方案讓人難以理解而且容易忘記。
(似乎只能通過外加BFC創造不同的BFC來避免...)
例二
如果說第一個例子一定程度上還算符合直覺,那第二個例子的情況老實說我是不能理解的...一度讓我以為是出了bug...但了解了Margin Collapse的產生條件之后,相信你能明白為何會出現這種現象以及如果消除它。
例三
第三個例子主要和BFC有關。一般說到"清除浮動"就會想到clear:both;
overflow:hidden;
之類.但是給父元素設置浮動居然也能"清除浮動"???估計深受浮動折磨的人看到這現象會進一步崩潰:為什么這樣也能清除浮動?到底有多少種清除浮動的方法?(當然“清除浮動”不是本文重點,若想進一步了解請看推薦閱讀)
所以這些現象到底跟標題中的Margin Collapse、BFC有什么關系呢?
上面例子的現象你都能給出解釋和解決方案嗎?如果都沒問題的話恭喜你,可以直接下拉到最后看推薦閱讀進行進一步閱讀了。如果一頭霧水的話也沒關系,我們一步一步來看是怎么回事。
想一下,有沒有覺得這有點像讀英語時每個單詞都認識,連在一起就看不懂的情況?這是因為缺乏相關的語法知識(當然也有可能是缺少上下文)
每一條CSS Rule就像一個個單詞,而相應的語法就是w3c制定的布局方案了(當然這里的類比不完全準確,自然語言是與上下文有關的語法,而CSS是與上下文無關的語法)
那w3c是怎么規定Margin Collapse的呢?
產生折疊的必備條件:margin必須是鄰接的!
而根據w3c規范,兩個margin是鄰接的必須滿足以下條件:
1.必須是處于常規文檔流(非float和絕對定位)的塊級盒子,并且處于同一個BFC(BFC產生條件見下文)當中。
2.沒有線盒,沒有空隙(clearance,本文不涉及),沒有padding和border將他們分隔開
3.都屬于垂直方向上相鄰的外邊距,可以是下面任意一種情況
- 元素的margin-top與其第一個常規文檔流的子元素的margin-top(即例二)
- 元素的margin-bottom與其下一個常規文檔流的兄弟元素的margin-top(即例一)
- height為auto的元素的margin-bottom與其最后一個常規文檔流的子元素的margin-bottom
- 高度為0并且最小高度也為0,不包含常規文檔流的子元素,并且自身沒有建立新的BFC的元素的margin-top和margin-bottom
以上的條件意味著下列的規則:
- 創建了新的BFC的元素(BFC元素的產生條件見下文)與它的子元素的外邊距不會折疊
- 浮動元素不與任何元素的外邊距產生折疊(包括其父元素和子元素)
- 絕對定位元素不與任何元素的外邊距產生折疊
- inline-block元素不與任何元素的外邊距產生折疊
- 一個常規文檔流元素的margin-bottom與它下一個常規文檔流的兄弟元素的margin-top會產生折疊,除非它們之間存在間隙(clearance)。
- 一個常規文檔流元素的margin-top 與其第一個常規文檔流的子元素的margin-top產生折疊,條件為父元素不包含 padding 和 border ,子元素不包含 clearance。
- 一個 'height' 為 'auto' 并且 'min-height' 為 '0'的常規文檔流元素的 margin-bottom 會與其最后一個常規文檔流子元素的 margin-bottom 折疊,條件為父元素不包含 padding 和 border ,子元素的 margin-bottom 不與包含 clearance 的 margin-top 折疊。
一個不包含border-top、border-bottom、padding-top、padding-bottom的常規文檔流元素,并且其 'height' 為 0 或 'auto', 'min-height' 為 '0',其里面也不包含行盒(line box),其自身的 margin-top 和 margin-bottom 會折疊。
折疊的結果:
兩個相鄰的外邊距都是正數時,折疊結果是它們兩者之間較大的值。
兩個相鄰的外邊距都是負數時,折疊結果是兩者絕對值的較大值。
兩個外邊距一正一負時,折疊結果是兩者的相加的和。
摘自w3plus: http://www.w3cplus.com/css/understanding-bfc-and-margin-collapse.html ? w3cplus.com
BFC(Block Formatting Contexts,塊級上下文)的產生條件
BFC在CSS3中改為Flow Root,觸發條件多了下面加粗的部分
- 浮動元素,float 除 none 以外的值
- 絕對定位元素,position(absolute,fixed)
- display 為以下其中之一的值 inline-blocks,table-cells,table-captions
- overflow 除了 visible 以外的值(hidden,auto,scroll)
注:"display:table" 本身并不產生 BFC,但它會產生匿名框,匿名框中包含 "display:table-cell" 的框會產成 BFC。
好的,上面一口氣丟了一大堆定義和條件出來,估計直接強行讀下來已經有點暈了。沒關系我們結合文章開頭的例子看一下吧。
例子解析
例一
顯然例一符合折疊的第一個條件:
1.必須是處于常規文檔流(非float和絕對定位)的塊級盒子,并且處于同一個BFC(BFC產生條件見下文)當中。
那要取消折疊自然就是破壞第一個條件啦,于是第一反應就是用overflow:hidden;
來把p標簽變成BFC。然后你就會開心的發現......并沒有什么變化。
為什么呢?我們再來讀一下第一個條件
"....處于同一個BFC當中。"
嗯,這里要搞清楚一個概念,BFC是一個區域,不是一個元素。
也就是說,你給p標簽設的overflow:hidden;
只是把p標簽里的內容設置成一個新的BFC,但是這個p標簽仍處于它之前所在BFC中這個事實沒有改變。
所以給出的解決方案是外面多包裹一個div再把這個div變成BFC(如overflow:hidden;
),這樣被包裹的p標簽處于它的父元素創建的BFC,而它的父元素此時和另一個p標簽處于同一個BFC,所以被包裹的p標簽與另一個p標簽自然不處于同一個BFC了(表達能力有限,這段寫的有點繞)
雖然要多加一個標簽,但其實仔細想想也沒有破壞語義性。因為正是多加了一個標簽才表示了被包裹的內容與外面的內容不處于同一個上下文。
例二
例二其實也是因為處于同一個BFC中而導致的,你可以試著給.parent加上overflow:hidden;
看看效果。demo中讓你嘗試加border或padding主要是因為這個折疊條件比較容易忽略。
不過產生BFC的條件多多少少都會有副作用,而這里其實只要讓子元素margin與父元素marigin不相鄰(折疊的最基本要求)就可以了
這里推薦的解決方案是使用偽元素:
.fathet:before{
content:"";
display: table;
}
首先content是偽元素生成的必要條件
這里的原理是:display:table;
會生成包含display:table-cell;
的匿名框(見上文BFC產生條件),從而產生了一個“沒有高度”的BFC區域(個人理解,如有錯誤歡迎指出)將子元素和父元素的margin分割開了。
例三
這里主要是因為BFC的特性所致
BFC有如下特性:
- BFC可以包含浮動元素
w3c原文:“'Auto' heights for block formatting context roots”,也就是 BFC 會根據子元素的情況自動適應高度,即使其子元素中包括浮動元素。
- BFC可以阻止元素被浮動元素覆蓋(即取消文字環繞的效果)
所以例三給父元素設置浮動后高度不再坍塌只是因為父元素的產生了BFC,而用其他產生BFC的方法同樣能包含浮動元素。
值得注意的是,雖然BFC有閉合浮動的效果,但并不推薦利用BFC閉合浮動。因為每一個用于產生BFC的條件都有一定程度的副作用,盡管當時可能副作用不明顯,但也是個很大的隱患。
推薦的“清除浮動”的方法是Nicolas Gallagher大神提出的micro clearfix hack:
.clearfix:after{
content:"";
display:table;
clear:both;
}
總結
- Margin Collapse不是bug,而是在CSS布局規則里面有規定的,具體出現情況見上文。
- BFC是一個區域,或者說是元素具有的一個特性,而不是元素本身。
- BFC能閉合浮動,但不應該用BFC來閉合浮動。
思考
先給body加上border,方便觀察。
試著給body標簽加上margin,再給body的第一個子元素(需要是塊級的)設置margin,看是否會發生折疊?
如果會發生折疊,也就是符合條件1
也就是處于同一個BFC中。這時如果給第一個子元素設置浮動又會發生什么呢?
實驗的結果是body無法包含浮動元素,這與BFC的特性矛盾。但同時卻能發生Margin Collapse,也就是處于同一個BFC中。這似乎有點矛盾。
當然我也沒想通這是為什么,如果有明白的人歡迎指教。我能想到的就只有因為body標簽太特殊,這是瀏覽器對它作出的特殊處理......
推薦閱讀
四篇文章搞定BFC(建議依次閱讀)
- 詳說 Block Formatting Contexts (塊級格式化上下文)
- W3C的官方定義
- Understanding Block Formatting Contexts in CSS
- Formatting contexts