深度剖析Margin塌陷,BFC,Containing Block之間的關係

殷榮檜發表於2018-05-04
作者:殷榮檜

參考資料中是我從網上看到的有關塌陷,BFC中寫的很好的幾篇。講的也很全面,教你如何用BFC,Containing Block(包含塊解決)margin塌陷的問題。但是有一點他們都沒有提及,就是為什麼會有這些東西出現。

一、問題一:為什麼會有Margin塌陷,是CSS設計時沒有考慮到的Bug,還是設計者有意為之?為什麼marign塌陷時,水平方向的不會發生塌陷呢?

二、問題二:CSS1.0中沒有BFC,Containing Block.為什麼要在CSS2.0中加入這兩個特性?

三、問題三:CSS1.0中沒有BFC,Containing Block,又是如何解決Margin塌陷的問題的?

四、問題四:.BFC除了解決Margin塌陷,還有別的作用嗎?

問題五:BFC是如何形成的?

以上的這些問題,如果不去了解設計者的思路,是不太容易理清這些的!理清這些有什麼用呢?目的只有一個:讓你更好的記住BFC以及Containing Block的用法。千萬不能死記硬背,硬揹著overflow:hidden能解決margin塌陷。下次換個環境你依然蒙圈,因為你不懂原理,不知道為什麼!接下來我把我這些天的學習總結記錄下,希望能對您有所幫助。

一、首先第一個問題:

1.為什麼會有Margin塌陷,是CSS設計時沒有考慮到的Bug,還是設計者有意為之?為什麼marign塌陷時,水平方向的不會發生塌陷呢?

規範1.0和2.0中分別是這樣說的:

CSS1.0
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
CSS2.0
無(因為在css1.0中已經規定)

CSS1.0中的規定寫的很明白,我們是故意這樣設計的,因為這樣在大多數情況下符合平面設計師的要求。這邊不禁有多了一個疑問,你(CSS設計者)咋知道設計師喜歡這樣呢。再說了,要合併也不要你預設就合併了啊,我自己寫明就是了,你為什麼要多此一舉呢,比如我要垂直間距20px就寫成下面這樣:

<div style="background-color:tomato;width:100px;height:100px;margin-bottom:20px;">
hello world
</div>
<div style="background-color:#bbb;width:100px;height:100px;margin-top:0px;">
hello world
</div>

我要垂直間距40px就寫成下面這樣:

<div style="background-color:tomato;width:100px;height:100px;margin-bottom:20px;">
hello world
</div>
<div style="background-color:#bbb;width:100px;height:100px;margin-top:20px;">
hello world
</div>

你偏要幫我把間距(原本20px+20px=40px)應改成20px,還說這樣更符合設計師的要求.好像有點說不過去啊,很多人踩過這樣的坑(你規範中說好的margin-top就是距離上邊界的距離,margin-bottom就是距離下邊界的距離,兩個一起用的時候誰知道你就不按套路出牌了)。為什麼CSS的制定者們要制定這樣一個坑呢,還找了個符合設計師設計的理由呢?一開始我也蒙了,然後我就去stackoverflow上問Why the css specification...。一個老程式設計師給了我答案:

There's a historic reason. Mostly about how P(Paragraph) elements were rendered in the days before CSS. CSS needed to replicate the existing behaviour. But the behaviour still makes sense today for the reasons given in the question

原來是為了相容在發明CSS1.0之前使用的P標籤

在CSS之前的P標籤(上下都有相同的邊距)就是如下圖所示:

深度剖析Margin塌陷,BFC,Containing Block之間的關係

也就是P標籤天生就長這樣,也沒有什麼css來讓你改他的預設樣式。再來看看現在的P標籤(其實就是用css修飾的一個display:block;元素)長什麼樣

引自W3schools
p {
    display: block;
    margin-top: 1em;
    margin-bottom: 1em;
    margin-left: 0;
    margin-right: 0;
}
複製程式碼

P標籤只是一個殼,重點是要看裡面的CSS樣式,看見沒有,margin-top:1em;margin-bottom:1em;如果沒有margin塌陷,他長這樣:

深度剖析Margin塌陷,BFC,Containing Block之間的關係

中間的間距就比開始個結束的要大,顯然不好看(這就是css規範說的更符合設計師的設計),同時,顯然也與老的P標籤表現的不一致。那怎麼辦呢?這時CSS的制定者們想了一個主意,把這種情況作為一種特殊情況,另行規定,於是就誕生了margin collapse的官方規定(多規定的其他幾項也是為更好的符合平面設計的視覺效果):

首先是一個大前提:元素之間沒有被非空內容、padding、border 或 clear 分隔開。然後有符合下面幾種毗鄰情況:
top margin of a box and top margin of its first in-flow child
一個元素的 margin-top 和它的第一個子元素的 margin-top
bottom margin of box and top margin of its next in-flow following sibling
普通流中一個元素的 margtin-bottom 和它的緊鄰的兄弟元素的的 margin-top
bottom margin of a last in-flow child and bottom margin of its parent if the parent has ‘auto’ computed height
一個元素( height 為 auto )的 margin-bottom 和它的最後一個子元素的margin-bottom
top and bottom margins of a box that does not establish a new block formatting context and that has zero computed ‘min-height’, zero or ‘auto’ computed ‘height’, and no in-flow children
一個沒有建立 BFC、沒有子元素、height 為0的元素自身的 margin-top 和 margin-bottom

這就是margin collapse的由來,所以到這個時候你應該對margin collapse有的徹底的瞭解了。所有的解決margin collapse無非都是破壞了上述的margin collapse成立條件之一。

關於margin collapse,你也可以參考這篇文章www.w3cplus.com/css/underst…

二、接下來第二個問題:

2.CSS1.0中沒有BFC,Containing Block.為什麼要在CSS2.0中加入這兩個特性?

在解決這個問題之前:要了解CSS1.0中並沒有Position(static,absolute,relative,fixed)這個屬性,相當於都是static屬性。要知道,之前都是一個大的矩形Box套一個小的Box,每個box的都很安分,一個套一個,並沒有absolute,fixed這樣不受矩形Box model約束的怪物來擾亂Box的排列。

擾亂了之後帶來了一個問題:

之前一個在CSS1.0中一個div,p這樣的元素如果有margin-top這樣的屬性,並且值為30%時.

'margin-top'(css1.0中的定義)
Value: length | percentage | auto
Initial: 0
Applies to: all elements
Inherited: no
Percentage values: refer to width of the closest block-level ancestor (注意這邊了)

這個30%根據定義就是相對於closest block-level ancestor,說白了就是相對於包住他的那個Box的寬度。

那在css2.0中的width如果是百分比又是相對於誰呢?CSS2.0上是這樣寫的:

'margin-top', 'margin-bottom'(CSS2.0中的定義)
Value: | inherit
Initial: 0
Applies to: all elements except elements with table display types other than table-caption, table and inline-table
Inherited: no
Percentages: refer to width of containing block (注意這邊了)

可以看出CSS2.0中的width是相對於一個叫做containing block這樣的一個東西的。現在是時候去了解他了,在瞭解他之前,就要了解為什麼好端端的closest block-level ancestor不要了,改用containing block這個了。那是因為出現了Position:absolute | fixed這兩個攪局者。接下來就分析一下,為什麼攪局這的出現會帶來containing block這個副產物。

(1)為什麼css2.0中要增加position這樣的一個屬性。

設想現在你是一位前端工程師,用的是CSS1.0這套技術。產品要求你線上上已有的網站頁面(比如如下的網頁佈局頁面)新增一個永遠定位在螢幕右下方的“返回頂部”的按鈕。


深度剖析Margin塌陷,BFC,Containing Block之間的關係
深度剖析Margin塌陷,BFC,Containing Block之間的關係


沒有position:fixed這樣的屬性給你用。那估計夠你頭疼的,因為你這些佈局必然是在一個大的container 的div中,無論你在哪邊加,都必然會對現有的佈局造成擠壓。 如下圖:


深度剖析Margin塌陷,BFC,Containing Block之間的關係
深度剖析Margin塌陷,BFC,Containing Block之間的關係


無論你放在哪個div中,都將破壞原有的div結構,更致命的是,他不會定位在瀏覽器的視窗中,因為沒法實現相對於瀏覽器視窗的定位,所以沒法實現懸浮。所以,產品的這個簡單的需求在CSS1.0時代變得非常難以實現。類似的很多相對於瀏覽器視窗的懸浮定位都無法實現。

所以制定CSS2.0時,制定者們就想怎麼解決這個問題,於是就想到增加一個position的屬性,也就是css1.0中的相當於都預設設定了position:static這樣的一個屬性,另外再增加position:fixed這樣的一個屬性。他的定位就是相對於瀏覽器視窗的。那這個時候如果用left:50%時,這個50%肯定時相對於瀏覽器視窗的。不能還是CSS1.0中的refer to width of the closest block-level ancestor因為closet block-level ancestor並不一定就是瀏覽器視窗。所以,就想以個名字來稱呼這個瀏覽器視窗,就叫Containing Block吧。Containing Block就這樣誕生了。

相對於瀏覽器視窗的定位解決了。此時,還有一個問題困擾了CSS1.0時代的工程是很久了,比如,產品提出了這樣的一個需求:要求在一個設定了overflow:hidden的div中,也放一個“返回頂部”按鈕。如下圖所示:

深度剖析Margin塌陷,BFC,Containing Block之間的關係
深度剖析Margin塌陷,BFC,Containing Block之間的關係


這個怎麼解決呢?CSS1.0功能又捉襟見肘了。position:fixed也無法實現,那就再加屬性值position:absolute;並且讓他不僅僅是相對於ancestor box,你可以隨便指定他相對於誰定位,比如A元素。(只要設定A的position為非static就可以了)。那此時的這個A又怎麼稱呼呢?還叫containing block吧!

此時,containing block的概念逐漸清晰,我們再看一下CSS2.0對於containing boxd的定義:

The position and size of an element's box(es) are sometimes calculated relative to a certain rectangle, called the containing block of the element. The containing block of an element is defined as follows:
1.The containing block in which the root element lives is a rectangle called the initial containing block. For continuous media, it has the dimensions of the viewport and is anchored at the canvas origin; it is the page area for paged media. The 'direction' property of the initial containing block is the same as for the root element.
2.For other elements, if the element's position is 'relative' or 'static', the containing block is formed by the content edge of the nearest block container ancestor box.
3.If the element has 'position: fixed', the containing block is established by the viewport in the case of continuous media or the page area in the case of paged media.
4.If the element has 'position: absolute', the containing block is established by the nearest ancestor with a 'position' of 'absolute', 'relative' or 'fixed', in the following way:
1.In the case that the ancestor is an inline element, the containing block is the bounding box around the padding boxes of the first and the last inline boxes generated for that element. In CSS 2.1, if the inline element is split across multiple lines, the containing block is undefined.

2.Otherwise, the containing block is formed by the padding edge of the ancestor.
If there is no such ancestor, the containing block is the initial containing block.

怎麼樣,是不是和我們推斷的一樣。這就是Containing Block的誕生記。

知道了有Containing Block的原因之後,我們再來看一下,為什麼又會誕生BFC(Block Format Context)這樣的東西。

前面,我們提到CSS1.0,CSS2.0中都有預設的margin collapse,那我如果我們就是不想要這個塌陷呢!這個是後CSS的制定者們又想,能不能制定一個屬性,讓上下的兩個div隔離開來,就像孫悟空給唐僧用金箍棒畫的圈一樣,外界的妖魔鬼怪都無法進入,這樣讓他們的Margin不會塌陷合併。制定者們給金箍棒畫的這個圈取了個名字叫BFC。他們還制定了觸發BFC的條件,這要符合這些條件中的一個,就能請來孫悟空畫上隔離區。符合觸發BFC的條件如下:

Floats, absolutely positioned elements, block containers (such as inline-blocks, table-cells, and table-captions) that are not block boxes, and block boxes with 'overflow' other than 'visible' (except when that value has been propagated to the viewport) establish new block formatting contexts for their contents.

有了BFC就可已很好的解決margin collapse.這裡我就不細說了,你可以參考這篇文章:css中的BFC

3.CSS1.0中沒有BFC,Containing Block,又是如何解決Margin塌陷的問題的? 這個在第一個問題中提及到了,只要破壞margin collapse成立的任何一個條件就可以了。

4.BFC除了解決Margin塌陷,還有別的作用嗎?

就和愛迪生一開始發明燈泡只是為了照明,但後來被人們用到KTV渲染氣氛了一樣。BFC也一樣,發明他的時候是為了隔離,因為隔離誕生了很多作用,還是參考這樣的一篇文章css中的BFC

5.BFC是如何形成的?

1.根元素(整個頁面就是一個大的BFC);

2.float為 left | right;

3.overflow為 hidden | auto | scroll;

4.display為 inline-block | table-cell | table-caption | flex | inline-flex;

5.position為 absolute | fixed;


總結:至此你知道了為什麼在CSS中有Margin collapse,BFC,Containing Block.這些事物,相信這樣一定能比你硬記住這記住怎麼用會更牢靠。



參考資料:
理解CSS中的BFC(塊級視覺化上下文)[譯] css中的BFC 深入理解BFC和Margin Collapse CSS 中 block-level boxes、containing block、block formatting context 三者之間的區別和聯絡是怎樣的? CSS1.0官方規範 CSS2.0官方規範


相關文章