學習 BFC (Block Formatting Context)

網易考拉前端團隊發表於2017-09-12

什麼是BFC

BFC全稱是Block Formatting Context,即塊格式化上下文。它是CSS2.1規範定義的,關於CSS渲染定位的一個概念。要明白BFC到底是什麼,首先來看看什麼是視覺格式化模型。

視覺格式化模型

視覺格式化模型(visual formatting model)是用來處理文件並將它顯示在視覺媒體上的機制,它也是CSS中的一個概念。

視覺格式化模型定義了盒(Box)的生成,盒主要包括了塊盒、行內盒、匿名盒(沒有名字不能被選擇器選中的盒)以及一些實驗性的盒(未來可能新增到規範中)。盒的型別由display屬性決定。

塊盒(block box)

塊盒有以下特性:

  • 當元素的CSS屬性displayblocklist-itemtable時,它是塊級元素 block-level;
  • 視覺上呈現為塊,豎直排列;
  • 塊級盒參與(塊格式化上下文);
  • 每個塊級元素至少生成一個塊級盒,稱為主要塊級盒(principal block-level box)。一些元素,比如<li>,生成額外的盒來放置專案符號,不過多數元素只生成一個主要塊級盒。

行內盒(inline box)

  • 當元素的CSS屬性display的計算值為inlineinline-blockinline-table時,稱它為行內級元素;
  • 視覺上它將內容與其它行內級元素排列為多行;典型的如段落內容,有文字(可以有多種格式譬如著重),或圖片,都是行內級元素;
  • 行內級元素生成行內級盒(inline-level boxes),參與行內格式化上下文(inline formatting context)。同時參與生成行內格式化上下文的行內級盒稱為行內盒(inline boxes)。所有display:inline的非替換元素生成的盒是行內盒;
  • 不參與生成行內格式化上下文的行內級盒稱為原子行內級盒(atomic inline-level boxes)。這些盒由可替換行內元素,或 display 值為 inline-blockinline-table 的元素生成,不能拆分成多個盒;

匿名盒(anonymous box)

匿名盒也有份匿名塊盒與匿名行內盒,因為匿名盒沒有名字,不能利用選擇器來選擇它們,所以它們的所有屬性都為inherit或初始預設值;

如下面例子,會創鍵匿名塊盒來包含毗鄰的行內級盒:

<div>
    Some inline text
    <p>followed by a paragraph</p>
    followed by more inline text.
</div>複製程式碼

三個定位方案

在定位的時候,瀏覽器就會根據元素的盒型別和上下文對這些元素進行定位,可以說盒就是定位的基本單位。定位時,有三種定位方案,分別是常規流,浮動已經絕對定位。

常規流(Normal flow)

  • 在常規流中,盒一個接著一個排列;
  • 塊級格式化上下文裡面, 它們豎著排列;
  • 行內格式化上下文裡面, 它們橫著排列;
  • positionstaticrelative,並且floatnone時會觸發常規流;
  • 對於靜態定位(static positioning),position: static盒的位置是常規流佈局裡的位置
  • 對於相對定位(relative positioning),position: relative,盒偏移位置由這些屬性定義topbottomleftandright即使有偏移,仍然保留原有的位置,其它常規流不能佔用這個位置。

浮動(Floats)

  • 盒稱為浮動盒(floating boxes);
  • 它位於當前行的開頭或末尾;
  • 導致常規流環繞在它的周邊,除非設定 clear 屬性;

絕對定位(Absolute positioning)

  • 絕對定位方案,盒從常規流中被移除,不影響常規流的佈局;
  • 它的定位相對於它的包含塊,相關CSS屬性:topbottomleftright
  • 如果元素的屬性positionabsolutefixed,它是絕對定位元素;
  • 對於position: absolute,元素定位將相對於最近的一個relativefixedabsolute的父元素,如果沒有則相對於body

塊格式化上下文

到這裡,已經對CSS的定位有一定的瞭解了,從上面的資訊中也可以得知,塊格式上下文是頁面CSS 視覺渲染的一部分,用於決定塊盒子的佈局及浮動相互影響範圍的一個區域

BFC的建立方法

  • 根元素或其它包含它的元素;
  • 浮動 (元素的float不為none);
  • 絕對定位元素 (元素的positionabsolutefixed);
  • 行內塊inline-blocks(元素的 display: inline-block);
  • 表格單元格(元素的display: table-cell,HTML表格單元格預設屬性);
  • overflow的值不為visible的元素;
  • 彈性盒 flex boxes (元素的display: flexinline-flex);

但其中,最常見的就是overflow:hiddenfloat:left/rightposition:absolute。也就是說,每次看到這些屬性的時候,就代表了該元素以及建立了一個BFC了。

BFC的範圍

BFC的範圍在MDN中是這樣描述的。

A block formatting context contains everything inside of the element creating it that is not also inside a descendant element that creates a new block formatting context.

中文的意思一個BFC包含建立該上下文元素的所有子元素,但不包括建立了新BFC的子元素的內部元素。

這段看上去有點奇怪,我是這麼理解的,加入有下面程式碼,class名為.BFC代表建立了新的塊格式化:

<div id='div_1' class='BFC'>
    <div id='div_2'>
        <div id='div_3'></div>
        <div id='div_4'></div>
    </div>
    <div id='div_5' class='BFC'>
        <div id='div_6'></div>
        <div id='div_7'></div>
    </div>
</div>複製程式碼

這段程式碼表示,#div_1建立了一個塊格式上下文,這個上下文包括了#div_2#div_3#div_4#div_5。即#div_2中的子元素也屬於#div_1所建立的BFC。但由於#div_5建立了新的BFC,所以#div_6#div_7就被排除在外層的BFC之外。

我認為,這從另一方角度說明,一個元素不能同時存在於兩個BFC中

BFC的一個最重要的效果是,讓處於BFC內部的元素與外部的元素相互隔離,使內外元素的定位不會相互影響。這是利用BFC清除浮動所利用的特性,關於清除浮動將在後面講述。

如果一個元素能夠同時處於兩個BFC中,那麼就意味著這個元素能與兩個BFC中的元素髮生作用,就違反了BFC的隔離作用,所以這個假設就不成立了。

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.

In a block formatting context, boxes are laid out one after the other, vertically, beginning at the top of a containing block. The vertical distance between two sibling boxes is determined by the 'margin' properties. Vertical margins between adjacent block-level boxes in a block formatting context collapse.

In a block formatting context, each box's left outer edge touches the left edge of the containing block (for right-to-left formatting, right edges touch). This is true even in the presence of floats (although a box's line boxes may shrink due to the floats), unless the box establishes a new block formatting context (in which case the box itself may become narrower due to the floats).

簡單歸納一下:

  1. 內部的盒會在垂直方向一個接一個排列(可以看作BFC中有一個的常規流);
  2. 處於同一個BFC中的元素相互影響,可能會發生margin collapse;
  3. 每個元素的margin box的左邊,與容器塊border box的左邊相接觸(對於從左往右的格式化,否則相反)。即使存在浮動也是如此;
  4. BFC就是頁面上的一個隔離的獨立容器,容器裡面的子元素不會影響到外面的元素,反之亦然;
  5. 計算BFC的高度時,考慮BFC所包含的所有元素,連浮動元素也參與計算;
  6. 浮動盒區域不疊加到BFC上;

這麼多性質有點難以理解,但可以作如下推理來幫助理解:html的根元素就是<html>,而根元素會建立一個BFC,建立一個新的BFC時就相當於在這個元素內部建立一個新的<html>,子元素的定位就如同在一個新<html>頁面中那樣,而這個新舊html頁面之間時不會相互影響的。

上述這個理解並不是最準確的理解,甚至是將因果倒置了(因為html是根元素,因此才會有BFC的特性,而不是BFC有html的特性),但這樣的推理可以幫助理解BFC這個概念。

從實際程式碼來分析BFC

講了這麼多,還是比較難理解,所以下面通過一些例子來加深對BFC的認識。

例項一

<style>
    * {
        margin: 0;
        padding: 0;
    }
    .left{
        background: #73DE80;    /* 綠色 */
        opacity: 0.5;
        border: 3px solid #F31264;
        width: 200px;
        height: 200px;
        float: left;
    }
    .right{                        /* 粉色 */
        background: #EF5BE2;
        opacity: 0.5;
        border: 3px solid #F31264;
        width:400px;
        min-height: 100px;
    }
    .box{
        background:#888;
        height: 100%;
        margin-left: 50px;
    }
</style>
<div class='box'>
    <div class='left'> </div>
    <div class='right'> </div>
</div>複製程式碼

顯示效果:

綠色框('#left')向左浮動,它建立了一個新BFC,但暫時不討論它所建立的BFC。由於綠色框浮動了,它脫離了原本normal flow的位置,因此,粉色框('#right')就被定位到灰色父元素的左上角(特性3:元素左邊與容器左邊相接觸),與浮動綠色框發生了重疊。

同時,由於灰色框('#box')並沒有建立BFC,因此在計算高度的時候,並沒有考慮綠色框的區域(特性6:浮動區域不疊加到BFC區域上),發生了高度坍塌,這也是常見問題之一。

例項二

現在通過設定overflow:hidden來建立BFC,再看看效果如何。

.BFC{
    overflow: hidden;
}

<div class='box BFC'>
    <div class='left'> </div>
    <div class='right'> </div>
</div>複製程式碼

灰色框建立了一個新的BFC後,高度發生了變化,計算高度時它將綠色框區域也考慮進去了(特性5:計算BFC的高度時,浮動元素也參與計算);

而綠色框和紅色框的顯示效果仍然沒有任何變化。

例項三

現在,現將一些小塊新增到粉色框中,看看效果:

<style>
    .little{
        background: #fff;
        width: 50px;
        height: 50px;
        margin: 10px;
        float: left;
    }
</style>

<div class='box BFC'>
    <div class='left'> </div>
    <div class='right'>
        <div class='little'></div>
        <div class='little'></div>
        <div class='little'></div>
    </div>
</div>複製程式碼

由於粉色框沒有建立新的BFC,因此粉色框中白色塊受到了綠色框的影響,被擠到了右邊去了。先不管這個,看看白色塊的margin。

例項四

利用同例項二中一樣的方法,為粉色框建立BFC:

<div class='box BFC'>
    <div class='left'> </div>
    <div class='right BFC'>
        <div class='little'></div>
        <div class='little'></div>
        <div class='little'></div>
    </div>
</div>複製程式碼

一旦粉色框建立了新的BFC以後,粉色框就不與綠色浮動框發生重疊了,同時內部的白色塊處於隔離的空間(特性4:BFC就是頁面上的一個隔離的獨立容器),白色塊也不會受到綠色浮動框的擠壓。

總結

以上就是BFC的分析,BFC的概念比較抽象,但通過例項分析應該能夠更好地理解BFC。在實際中,利用BFC可以閉合浮動(例項二),防止與浮動元素重疊(例項四)。同時,由於BFC的隔離作用,可以利用BFC包含一個元素,防止這個元素與BFC外的元素髮生margin collapse。

參考

視覺格式化模型_MDN

塊格式化上下文_MDN

CSS之BFC詳解

W3C block-formatting

相關文章