寫在前面
margin的合併規則算是CSS盒模型裏最複雜部分,沒有之一。由於這部份內容涉及不少不太容易理解的概念,例如clearance(間隙)、normal flow/in-flow(常規流)、BFC(塊格式化上下文)、line box(行框)、inline box(行內框)、bidi(雙向環境)等等html
CSS盒模型不僅是7項水平屬性 + 7項垂直屬性:ide
margin border padding width/height
P.S.想起高跟鞋的梗——「不只有padding,今天還加了margin」佈局
相關的內容至少還包括:.net
context-box與border-boxcode
padding/margin百分比的計算方式orm
background與padding/margin/borderhtm
margin負值遞歸
margin合併作用域
盒模型是視覺格式化模型中的基礎單元,是CSS佈局模型中必不可少的一部分文檔
CSS盒模型描述了一個爲文檔樹中的元素生成的並根據視覺格式化模型進行佈局的矩形框
(引自8 盒模型)
因此,盒模型也是CSS在文檔樹之上創建的第一層抽象,是CSS佈局控制與文檔元素直接關聯的部分。而外邊距合併是直接影響垂直格式化的因素之一,有必要深刻理解
一.經典場景
下列例子中,假設UA沒有默認樣式表,未聲明的樣式屬性都依照規範取其初始值
另外,假設UA都是遵照CSS規範的
1.列表項間的外邊距合併
li { margin: 8px; } 那麼列表項之間的間距是多少? .li-case1 li { margin: 8px; /* 添個上內邊距 */ padding-top: 1px; } .li-case2 li { margin: 8px; /* 添個下邊框 */ border-bottom: 1px solid; }
在case1和case2中,列表項間距分別是多少?
2.深層嵌套的外邊距合併
/* 縮進表示對應文檔結的構嵌套關係 */ div.outer, div.container, div.content, div.inner { margin: 10px; min-width: 100px; min-height: 100px; }
這4個嵌套的div渲染結果是什麼樣子?
div.outer, div.container, div.content, div.inner { margin: 10px; min-width: 100px; min-height: 100px; /* 添個border */ border: 1px solid; }
如今呢?
div.outer, div.container, div.content, div.inner { margin: 10px; /* 刪掉min-width, min-height和border */ }
那麼如今呢?
3.帶間隙的外邊距合併
div.container { border-top: 1px solid; background: #ccc; margin-bottom: 60px; } /* 縮進表示對應文檔結的構嵌套關係 */ div.float { float: left; width: 100px; height: 50px; } div.following-float { clear: left; margin-top: 50px; } div.following-container { color: red; }
紅色文本頂端距.following-float底端的距離是多少?
div.container { border-top: 1px solid; background: #ccc; margin-bottom: 60px; } /* 縮進表示對應文檔結的構嵌套關係 */ div.float { float: left; width: 100px; height: 50px; } div.following-float { clear: left; /* 把50改爲49 */ margin-top: 49px; } div.following-container { color: red; }
如今呢?
再把50改爲0和51呢?又分別會出現什麼狀況?
P.S.這些問題的答案此刻仍是未知的,由於Demo還沒開始寫;-)那麼就有了足夠的時間容咱們認真猜一下
二.合併條件
什麼樣的外邊距會發生合併?
水平外邊距不合並。相鄰的垂直外邊距會合並,除了2種特殊狀況:
根元素盒的外邊距不合並
若是一個帶有間隙的元素的上外邊距與下外邊距相鄰,它的外邊距會和緊挨着的兄弟(元素)的相鄰外邊距合併,但合併後不會再和父級塊的下外邊距合併
第1條跳過,對根元素應用外邊距不在情理之中
第2條引入了一個新概念,叫「間隙」,英文名clearance,看樣子與clear屬性有關,實際符合直覺,是指clear屬性致使元素位置移動造成的間隙,見CSS規範9 視覺格式化模型。隱含兩個關鍵點:
具備clear屬性
而且(clear屬性)讓元素位置發生移動了
若是知足這兩個條件,就說一個元素帶有間隙
注意:若是應用了clear屬性,元素的實際位置不變,好比經過margin-top把元素放到那個位置的,此時元素自身的佈局位置與clear效果位置同樣(即clear屬性沒有帶來額外的空間佔用,所謂的間隙),就不具備間隙。反過來,若是應用clear屬性,致使元素的實際位置發生了變化,即元素上方有一部分空間是clear屬性帶來的,那麼就算帶有間隙
帶有間隙還不夠,還要該元素的上下外邊距相鄰(意味着元素的實際高度爲0,且沒有padding, border),同時知足的話,這個元素的外邊距合併會受到限制:其外邊距只和緊挨着的兄弟的相鄰外邊距合併,合併後的結果不會再和父級塊的下外邊距發生合併
P.S.到這裏有挑戰經典場景3的入場券了,但還差得很遠
「相鄰」的定義
兩個外邊距在什麼狀況纔算「相鄰」?
都屬於流內(in-flow)塊級盒,處於同一個塊格式化上下文
沒有行框(line box),空隙,內邊距和邊框把它們隔開
都屬於垂直相鄰框邊界(vertically-adjacent box edges)
3句話4個新概念,深度優先過一下
流內
流內/流外(in-flow/out-of-flow)是指是否用常規流定位方案來佈局該元素
繼續深度優先,定位方案分3種:
常規流。包括塊格式化、行內格式化和相對定位
浮動。從常規流的位置取出來向左/右移
絕對定位。從常規流中脫離出去,根據其包含塊肯定自身位置
元素既沒有浮動(float屬性的應用值爲none),也沒有絕對定位(position屬性的應用值不爲absolute),而且不是根元素,那就按常規流來佈局,就屬於流內元素,不然就是流外元素
塊格式化上下文
浮動,絕對定位的元素,非塊盒的塊容器(例如inline-blocks,table-cells和table-captions),以及’overflow’不爲’visible’的塊盒(當該值已被傳播到視口時除外)會爲其內容創建新的塊格式化上下文
在一個塊格式化上下文中,盒在豎直方向一個接一個地放置,從包含塊的頂部開始。兩個兄弟盒之間的垂直距離由’margin’屬性決定
也就是說,若是沒人創建新的BFC,那麼就處於當前BFC。像JS做用域同樣,默認你們都位於最外層做用域(最外層塊格式化上下文),遇到普通塊級盒就放進塊格式化上下文,遇到特殊的(浮動,絕對定位的等等)就新建一層做用域(創建新的塊格式化上下文),它裏面的元素都放進這個內層做用域(新的塊格式化上下文)
佈局完成後從格式化上下文的角度來看,就是一系列嵌套的BFC,每一個BFC負責管理一組塊盒(或者說塊級元素)的佈局
注意:這裏不提行內格式化上下文,由於區分出不一樣的行內格式化上下文沒有太大意義(規範定義中,沒有關於跨行內格式化上下文的特殊場景)。那麼,何時會建立新的行內格式化上下文?,根據規範,只在塊容器只含有行內級盒時才建立一個新的行內格式化上下文,不像BFC能夠顯式地強制建立
P.S.關於什麼時候會建立新行內格式化上下文的更多討論,請查看When does a box establish an inline formatting context?
行框
包含來自同一行的盒的矩形區域叫作行框
一個行框老是足夠高,可以容納它包含的全部盒。
行框是CSS對行的抽象表示,每行元素都處於同一個行框裏。若是太長放不下出現自動換行,那麼就會爲下一行再建立一個行框。另外一方面,行框不是純粹的抽象定義,它具備寬度和高度,用於決定行佈局
相鄰外邊距之間「沒有行框」能夠簡單理解爲沒有行內元素把它們隔開
垂直相鄰框邊界
下列4種場景知足外邊距都屬於垂直相鄰框邊界的狀況:
盒的上外邊距與其第一個流內(in-flow)孩子的上外邊距
盒的下外邊距與其下一個流內緊挨着的兄弟的上外邊距
最後一個流內孩子的下外邊距與其height計算值爲’auto’的父元素的下外邊距
盒的上外邊距和下外邊距,要求該盒沒有創建新的塊格式化上下文,而且’min-height’計算值爲0,’height’計算值爲0或’auto’,尚未流內孩子
看起來太長,咱們簡化條件,假設都是流內元素的話,那麼:
父子:父元素上外邊距與長子上外邊距
兄弟:元素的下外邊距與右兄弟的上外邊距
父子:幺兒的下外邊距與父元素的下外邊距
自身:0高「真空」元素的上外邊距與下外邊距
P.S.這裏的「真空」是指——把薯片抽成真空。要麼裏面什麼都沒有,要麼流內孩子都被抽離了
也就是說,「相鄰外邊距」的位置定義具體分3種狀況:父子,兄弟和自身(自身上下外邊距合併是比較奇特的)
從新理解「相鄰」與外邊距合併
有了前面的概念鋪墊,如今咱們把零散的點整合起來,先從新定義「相鄰」:
父子,兄弟或元素自身的外邊距緊挨在一塊兒就是「相鄰」
還有一個關鍵點:緊挨。就是說這兩個外邊距沒被「牆」隔開,「牆」分3種:
種族:雙方必須都是流內塊級盒
信仰:處於同一個塊格式化上下文
地域:兩者之間沒有行框(line box),空隙,內邊距和邊框
到這裏,「相鄰」已經很清楚了,咱們再反推外邊距合併的定義:
非根元素的相鄰垂直外邊距會合並,帶有間隙的話,合併受限
受限是指帶有間隙元素自身上下邊距相鄰的話,只能與兄弟元素的外邊距合併,沒法和父元素的下外邊距合併
三.合併條件推論
根據外邊距合併的發生條件,有8條推論:
浮動的盒與任何其它盒之間的外邊距不會合並(甚至一個浮動盒與它的流內子級之間也不會)
創建了新的塊格式化上下文的元素(例如,浮動盒與’overflow’不爲’visible’的元素)的外邊距不會與它們的流內孩子合併
絕對定位的盒的外邊距不會合並(甚至與它們的流內孩子也不會)
內聯塊盒的外邊距不會合並(甚至與它們的流內孩子也不會)
流內塊級元素的下外邊距總會與它的下一個流內塊級兄弟的上外邊距合併,除非該兄弟(元素)具備間隙
流內塊級元素的上外邊距會與它的第一個流內塊級孩子的上外邊距合併,條件是該元素沒有上邊框和上內邊距,而且其孩子不具備間隙
一個’height’爲’auto’而且’min-height’爲0的流內塊級盒的下外邊距會與它的最後一個流內塊級孩子的下外邊距合併,條件是該盒沒有下內邊距和下邊框,而且其孩子的下外邊距沒有與具備間隙的上外邊距合併
盒自身的外邊距也會合並,條件是’min-height’屬性爲0,既沒有上下邊框,也沒有上下內邊距,’height’爲0或’auto’,且不含行框的話,那麼其全部流內孩子的外邊距(若是存在的話)都會合並
簡化總結,不過4條:
非流內(絕對定位或浮動)不合並
觸發新BFC建立(浮動,絕對定位元素,非塊盒的塊容器以及’overflow’不爲’visible’的某些塊盒)不與孩子合併
非塊級盒(內聯塊)不合並
通常狀況下,兄弟元素的下上外邊距,父子元素的上外邊距、下外邊距,元素自身的上下外邊距會合並
前3點針對「相鄰」的前提條件(流內,同BFC,塊級盒),第4點是對4種「相鄰」場景的轉述,展開就是8條推論
四.合併行爲
兩個相鄰外邊距發生合併後,造成的外邊距叫摺疊外邊距
P.S.collapsed margin故意譯做摺疊表示結果,與合併的動做區分開
外邊距合併有2個特色:
遞歸:即深層合併。合併一次後,再檢查與合併結果相鄰的外邊距有沒有能合併的,有的話接着合
貪婪:追求最寬合併結果。兩個margin正值取最大值,兩個負值取絕對值的最大值
對於遞歸特性,「相鄰」的定義擴展出一條遞歸公式:
摺疊外邊距也能與另外一個外邊距相鄰,只要其外邊距的任意一部分與那個外邊距相鄰就算
貪婪與外邊距合併結果計算方式有關,由於margin容許負值,狀況稍微複雜一點:
都是正值,直接求兩者最大值
一正一負,相加求和
都是負值,求兩者絕對值的最大值
例如:
ul {margin-bottom: -15px;} /* 縮進表示對應文檔結的構嵌套關係 */ li {margin-bottom: 20px;} h1 {margin-top: -18px;}
那麼h1與最後一個li的垂直距離爲20 + -max(|-15|, |-18|) = 2px
不管對正值仍是負值,求最大值的原則都是讓合併結果儘可能寬(絕對值更大的負值能讓元素內容偏移出去更遠的距離),即貪婪性
五.在線Demo
Demo地址:http://ayqy.net/temp/margin-collapse.html
P.S.答案都在Demo裏,解釋都在源碼裏
參考資料
When does a box establish an inline formatting context?:問題評論頗有價值,有助於理解行內格式化上下文
Margin collapse and clearance:clearance的示例
帶有間隙的外邊距合併示例:要用Firefox看,由於Chrome和Safari不遵照規範
Collapsing Margins:看了本文就不用看這個了