CSS中有個重要的概念BFC,搞懂BFC可以讓我們理解CSS中某些原本詭異(??)的地方。
1. 簡介
在解釋BFC之前,先說一下文件流。我們常說的文件流其實分為定位流、浮動流、普通流三種。而普通流其實就是指BFC中的FC。FC(Formatting Context),直譯過來是格式化上下文,它是頁面中的一塊渲染區域,有一套渲染規則,決定了其子元素如何佈局,以及和其他元素之間的關係和作用。常見的FC有BFC、IFC,還有GFC和FFC。
BFC(Block Formatting Context)塊級格式化上下文,是用於佈局塊級盒子的一塊渲染區域。MDN上的解釋:BFC是Web頁面 CSS 視覺渲染的一部分,用於決定塊盒子的佈局及浮動相互影響範圍的一個區域。
注意:一個BFC的範圍包含建立該上下文元素的所有子元素,但不包括建立了新BFC的子元素的內部元素。這從另一方角度說明,一個元素不能同時存在於兩個BFC中。因為如果一個元素能夠同時處於兩個BFC中,那麼就意味著這個元素能與兩個BFC中的元素髮生作用,就違反了BFC的隔離作用。
2. 三種文件流的定位方案
常規流(Normal flow)
- 在常規流中,盒一個接著一個排列;
- 在塊級格式化上下文裡面, 它們豎著排列;
- 在行內格式化上下文裡面, 它們橫著排列;
- 當position為static或relative,並且float為none時會觸發常規流;
- 對於靜態定位(static positioning),position: static,盒的位置是常規流佈局裡的位置;
- 對於相對定位(relative positioning),position: relative,盒偏移位置由top、bottom、left、right屬性定義。即使有偏移,仍然保留原有的位置,其它常規流不能佔用這個位置。
浮動(Floats)
- 左浮動元素儘量靠左、靠上,右浮動同理
- 這導致常規流環繞在它的周邊,除非設定 clear 屬性
- 浮動元素不會影響塊級元素的佈局
- 但浮動元素會影響行內元素的佈局,讓其圍繞在自己周圍,撐大父級元素,從而間接影響塊級元素佈局
- 最高點不會超過當前行的最高點、它前面的浮動元素的最高點
- 不超過它的包含塊,除非元素本身已經比包含塊更寬
- 行內元素出現在左浮動元素的右邊和右浮動元素的左邊,左浮動元素的左邊和右浮動元素的右邊是不會擺放浮動元素的
絕對定位(Absolute positioning)
- 絕對定位方案,盒從常規流中被移除,不影響常規流的佈局;
- 它的定位相對於它的包含塊,相關CSS屬性:top、bottom、left、right;
- 如果元素的屬性position為absolute或fixed,它是絕對定位元素;
- 對於position: absolute,元素定位將相對於上級元素中最近的一個relative、fixed、absolute,如果沒有則相對於body;
3. BFC觸發方式
- 根元素,即HTML標籤
- 浮動元素:float值為
left
、right
- overflow值不為 visible,為
auto
、scroll
、hidden
- display值為
inline-block
、table-cell
、table-caption
、table
、inline-table
、flex
、inline-flex
、grid
、inline-grid
- 定位元素:position值為
absolute
、fixed
注意 display:table也可以生成BFC的原因在於Table會預設生成一個匿名的table-cell,是這個匿名的table-cell生成了BFC。
4. 約束規則
瀏覽器對BFC區域的約束規則:
- 生成BFC元素的子元素會一個接一個的放置。
- 垂直方向上他們的起點是一個包含塊的頂部,兩個相鄰子元素之間的垂直距離取決於元素的margin特性。在BFC中相鄰的塊級元素的外邊距會摺疊(Mastering margin collapsing)。
- 生成BFC元素的子元素中,每一個子元素左外邊距與包含塊的左邊界相接觸(對於從右到左的格式化,右外邊距接觸右邊界),即使浮動元素也是如此(儘管子元素的內容區域會由於浮動而壓縮),除非這個子元素也建立了一個新的BFC(如它自身也是一個浮動元素)。
規則解讀:
- 內部的Box會在垂直方向上一個接一個的放置
- 內部的Box垂直方向上的距離由margin決定。(完整的說法是:屬於同一個BFC的兩個相鄰Box的margin會發生摺疊,不同BFC不會發生摺疊。)
- 每個元素的左外邊距與包含塊的左邊界相接觸(從左向右),即使浮動元素也是如此。(這說明BFC中子元素不會超出他的包含塊,而position為absolute的元素可以超出他的包含塊邊界)
- BFC的區域不會與float的元素區域重疊
- 計算BFC的高度時,浮動子元素也參與計算
5. 作用
BFC是頁面上的一個隔離的獨立容器,容器裡面的子元素不會影響到外面元素,反之亦然。我們可以利用BFC的這個特性來做很多事。
5.1 阻止元素被浮動元素覆蓋
一個正常文件流的block元素可能被一個float元素覆蓋,擠佔正常文件流,因此可以設定一個元素的float、display、position值等方式觸發BFC,以阻止被浮動盒子覆蓋。
5.2 可以包含浮動元素
通過改變包含浮動子元素的父盒子的屬性值,觸發BFC,以此來包含子元素的浮動盒子。
注意,這裡觸發BFC並不能阻止其它形式的脫離文件流的元素覆蓋正常流元素。
BFC內部其他形式脫離文件流(absolute fixed)
5.3 阻止因為瀏覽器因為四捨五入造成的多列布局換行的情況
有時候因為多列布局採用小數點位的width導致因為瀏覽器因為四捨五入造成的換行的情況,可以在最後一列觸發BFC的形式來阻止換行的發生。比如下面栗子的特殊情況
5.4 阻止相鄰元素的margin合併
屬於同一個BFC的兩個相鄰塊級子元素的上下margin會發生重疊,(設定writing-mode:tb-rl時,水平margin會發生重疊)。所以當兩個相鄰塊級子元素分屬於不同的BFC時可以阻止margin重疊。 這裡給任一個相鄰塊級盒子的外面包一個div,通過改變此div的屬性使兩個原盒子分屬於兩個不同的BFC,以此來阻止margin重疊。
但是這裡有個疑問: 如果外面包一層div,設定能觸發BFC的任何屬性都能阻止相鄰元素的margin合併。因為分屬不同BFC不會發生margin合併。 而如果在外面不包一個div的話,當設定display為inline-block、inline-flex、table-captain,和position為absolute、fixed,float為left、right是可以阻止margin合併的。這裡問題來了:
我們知道設定position和float會讓元素脫離文件流並且又建立新的BFC,所以兩個元素就不是相鄰元素了,因此可以阻止相鄰元素margin合併,但是inline-block、inline-flex、inline-grid、table-captain為什麼可以呢?如果有人知道為什麼,請告知~
網上的帖子大多深淺不一,甚至有些前後矛盾,在下的文章都是學習過程中的總結,如果發現錯誤,歡迎留言指出~
參考:
PS:歡迎大家關注我的公眾號【前端下午茶】,一起加油吧~