用 clearfix 解決 Bootstrap grid system 跑版問題,以及其背後原理

An-Chieh Kuo (郭安傑/關節)
8 min readDec 22, 2017

--

文前備註:本文當時是以 Bootstrap 3 作討論,與現在最新的 Bootstrap 4 在語法上略有差異,不過機制上相同,文章最後將會列出文內提及程式碼在兩版本中差異。

問題描述:

在使用 Bootstrap 3過程中,發現 grid system 雖然能夠快速排版,但如果 div 內的物件大小不一時,會使 div 長短不同造成跑版。例如下圖是以 row 包起來的頁面,原本預期輸出3x3卡片式頁面,結果因為圖片長短不一導致跑版。

解決方法:clearfix

解決方法是在每個區間下加入下列程式碼(❶) :

<div class="clearfix hidden-xs"></div>

本例是 col-md-4,也就是三個為一排,所以每三個卡片下要加入 clearfix。下面這段重複三次就能達成原本在 md 大小畫面上預期輸出3x3卡片式頁面。

<div class="col-md-4">卡片1的內容</div>
<div class="col-md-4">卡片2的內容</div>
<div class="col-md-4">卡片3的內容</div>
<div class=”clearfix hidden-xs”></div>

用 Chrome 的 DevTools 會發現,只是插一行空的 clearfix 而已,竟然就把三個卡片給包起來了! 到底怎麼做到的?

跑版的罪魁禍首: float

在問 clearfix 怎麼做到之前,要先探討"為什麼會發生這個問題?"。如果用 DevTools 點開 col-md-4,會發現其實每張卡片都是以 float: left 進行排版。也因為 float 的特性,才會有這個問題。

當橫向沒有空間的時候,浮動元素會向下移動,直到他放得下為止(❷)

以前面的例子來說(見下圖示),前三張卡占滿了頁面,沒空間讓第四張(#4)放,所以就沿著#3的邊緣向下移動,直到他可以往左擠進頁面裡,然後當左邊碰到#2時便停下來了。當發生這種狀況時,就跑版了。

把 grid system 的 css 設定放進 codepen 後跑出來的結果就會像下面這樣:

clearfix 的功能: 清除浮動(float)

DevTools 點開 clearfix 查看 css 可以看到設定如下:

.clearfix::before, .clearfix::after{
content: " ";
display: table;
}
.clearfix::after{
clear: both;
}

也就是以 ① :before, ::after 偽元素搭配 ② display: table 以及 ③ clear: both 達成

clearfix 的css設定放搭配 grid system 後跑出來的結果如下,先看過之後再來解釋各項功用。( contentclearfix 包夾的內容有修改過,方便觀察)

① ::before 與 ::after 偽元素

::before/::after 的作用簡單說就是在原本的元素之前/之後加入內容(❸)。下面是加入 ::before/::after 但沒有設定 displayclear 的狀況。如果試著用滑鼠框選,會發現 div.bf 包夾的字可以被選取,但以 :before/:after顯示的卻無法,也就是 ::before/::after 的內容是像幽靈一樣看得到摸不到的東西。所以 clearfix::before/::after 設定內容為空格,可以幫助帶入所需的屬性而不影響內容。

div.bf 在設定 ::before/::after 屬性前,如果不包夾東西,它的高度為0,貼在 container 頂端,且寬度等同 container。加入 ::before/::after 屬性後,div.bf 因為有包含了 ::before/::after 兩個 inline 元素,高度被撐開(如下圖)。所以前面以為是把三張卡片包起來,事實上只是因為 clearfix 高度較高,看起來不含任何元素,且跟浮動的卡片屬於不同層,導致看起來像是程式碼寫在下面卻能夠把上面的東西包起來。

②display: table

接下來在 ::before/::after 內加入 display: table,產生結果入下面所示。::before/::after 因為設定成 display: table,進而產生匿名框並觸發BFC(❹,❺),所以變成垂直排列。

③ clear: both

經由設定 clear: left / clear: right / clear: both,可以用來確保當前元素左側/右側/兩側不會有浮動元素(❻)。不過 clear 有兩個特性,一是只對塊級元素( block-level elements )發生作用(❷之9.5.2),二是只對元素本身布局起作用(❻),這兩點會牽扯到為什麼要設定 display

把前面①的程式碼在 :after 中加入 clear: both,會發現並沒有奏效,因為 before, hey, after 預設 displayinline,並不屬於塊級元素,所以 clear 不會作用。

再回第一個 codepen 中看,經由 display: table 設定,div.clearfix 變成了三個塊級元素。after 受到 clear: both 的影響,需要保持左右兩側沒有浮動元素的狀態,所以將自身往下移動,直到靠到 container 左側,這時左邊就沒有浮動元素了。同理,因為右側不能有浮動元素,造成屬性為靠左浮動的 box4 需要換行並向左靠,所以 box4 就停在了 after 的下方。

其實 row 裡面就有 clearfix

Bootstrap 對於 grid system 設定中有個使用條件: column 必須要放在 row 裡面,那是因為 row 帶有 clearfix 的功能(❼),從 DevTools 也能看到同樣設定。也因為這樣,當你產生新的 row 時,裡面的第一個 col 一定會在最左邊(如果沒 offset ),且一定會在上一個 row 的下面。

所以最前面提到插入 div.clearfix 的解法,還有另外一種方法就是改寫成每列都用 row 包起來。當需要使用 RWD 時,每列顯示 column 數量會不同,這時就需要判斷是否要清除浮動。以 div.clearfix 的方法來說只要判斷是否要再加入 hidden-smhidden-md 還是 hidden-lg 就好,一行即可解決;但是改寫成用 row 包夾時卻要雙倍工,程式碼會變得更雜,所以最佳解仍是 div.clearfix

補充

如果試著把 ::before 內的設定刪掉,或是把 display: table 換成 display: block 的話其實還是能夠清除浮動,因為:

  1. ::before 內的設定其實是為了防止 margin collapse
  2. display: table 是為了顧及在 IE 6/7 上顯示的問題(❽)。

2018.07 更新

改版成 Bootstrap 4 後,有些語法與 Bootstrap 3 有差異:

  1. Responsive utilities: hidden-xs 之類的
<!-- Bootstrap 3 使用 -->
<div class="clearfix hidden-xs"></div>
<!-- Bootstrap 4 使用 -->
<div class="clearfix d-none d-sm-block"></div>

詳細差異請見參考資料9

2. clearfix 的內容變更:

/* Bootstrap 3 使用 */
.clearfix::before, .clearfix::after{
content: " ";
display: table;
}
.clearfix::after{
clear: both;
}
/* Bootstrap 4 使用 */
.clearfix::after{
content: " ";
display: block;
clear: both;
}

Bootstrap 4 拿掉了 ::before 並將 display 改為 block (見參考資料10)。由於 display: table 可能會產生一些問題(可見參考資料11),而他的出現又是為了 IE 6/7 支援性的關係,微軟都要把 IE 6/7淘汰掉了,換 display: block 也是正常的。

參考資料:

  1. Bootstrap必知!內建clearfix解決網格DIV區塊長短腳問題
  2. CSS2.1 Chapter 9
  3. CSS 偽元素 ( before 與 after )
  4. 详解 清除浮动 的多种方式(clearfix)
  5. 视觉格式化模型
  6. 详解CSS float属性
  7. Bootstrap 3.3 - make-row
  8. A new micro clearfix hack
  9. Bootstrap 4 -Hiding elements
  10. Bootstrap 4 -clearfix
  11. The very latest clearfix reloaded

--

--