CSS之浮動Float

發表於2024-02-25

前言

提到浮動,前端的小夥伴肯定都不陌生,但是隨著彈性佈局等等一些更好用的標準出來後,用在佈局方面少了很多,當初我剛開始接觸前端的時候,很習慣用浮動來給元素改變定位,當時還並不是很流行flexbox佈局,很多佈局會透過浮動來實現,但是使用浮動來佈局會產生一些副作用,比如雖然使用浮動可以使元素向左或向右靠齊,但會造成高度坍塌,當時的我並不太瞭解其中的緣由,只是機械地從網上搜尋到一些解決高度坍塌的程式碼,到現在也不能算是瞭解的很透徹,只能算是比剛開始做前端的時候多瞭解了一點。

脫離文件流

在說到浮動的時候,很多地方都會說,它們脫離了文件流,那麼正常情況下文件流是怎麼樣的呢?

寫過HTML的小夥伴應該都瞭解,HTML中的元素應用的是預設的流式佈局;假設頁面上有一個div,然後還有一個span,如果我們不編寫額外的樣式改變它們的佈局方式,它們會按照預設的規則佈局,div是塊狀元素,預設佔滿行,即使給div設定了寬度,也是佔著一行,如果有多個div會縱向一個個排布,而span是行內元素,通常行內元素會放置在塊容器內部,如果有多個span會預設橫向從左向右一個挨一個排布。

應用了浮動的元素就不受流式佈局的控制了。比如應用了float: left;div,它不再會佔滿一整行,應用了float: right;span,可以從右向左排布。

那麼浮動有什麼特點,清除浮動又是什麼意思呢?

浮動

前端有幾個較為有名的佈局方式,比如聖盃佈局、雙飛翼佈局等等,使用float是實現這些佈局的方式之一,當然現在我們可以使用更便捷的方式來實現,比如flex彈性佈局、grid網格佈局等等,在這方面浮動的應用應該少了很多,但其實浮動它原本設計之初應該就不是為了這些佈局(個人觀點,暫未考證),這從它的名稱上就可以窺見。

我們可以把文件想象成一個水面,而浮動元素就好比是浮在水面上的船。水波環繞船體,就像浮動元素被其他內容環繞一樣。比如下面這張圖:

設計float屬性主要就是實現這種效果,這從規範文件中的描述就能看出:

The most interesting characteristic of a float (or "floated" or "floating" box) is that content may flow along its side (or be prohibited from doing so by the 'clear' property). Content flows down the right side of a left-floated box and down the left side of a right-floated box.

浮動框(或 "浮動 "或 "浮動 "框)最有趣的特點是,內容可以沿其側面流動(或透過 "清除 "屬性禁止流動)。內容可以沿著左浮動框的右側流動,也可以沿著右浮動框的左側流動。

這裡流動的內容就可以比作流動的水,所以我們經常能看到使用float實現文字環繞效果的例子。比如規範文件中給出的例子:

只能說早些時候的CSS還不夠完善,不能滿足一些特殊的佈局需求,而float恰好誤打誤撞可以滿足,這算是一種css hack吧。

當使用float:left;或者float: right;宣告某個節點後,這個節點就變成了水面上的船,一個自身重量為0的船,完全浮在水面上,當船上沒有任何東西時,它不會影響到水面,而當我們往其中增加內容之後,船就會向水面下沉一點,從而影響到水面。產生類似於規範文件中的以下描述:

Since a float is not in the flow, non-positioned block boxes created before and after the float box flow vertically as if the float did not exist. However, the current and subsequent line boxes created next to the float are shortened as necessary to make room for the margin box of the float.

由於浮動框不在流中,在浮點框之前和之後建立的非定位塊框會垂直流動,就像浮動框不存在一樣。不過,在浮動框旁邊建立的當前和後續行方框會根據需要縮短,以便為浮動框的margin box留出空間。

float會影響所在行的行方框,也就是line boxes。就類似於影響到水面的面積。

清除浮動

在使用浮動用於佈局之後,我們常常需要清除浮動,那麼清除浮動是什麼意思呢?這裡可以繼續用前面浮在水面上的船來舉例子,雖然可能不算很貼切。

船在水面上是浮動著的,隨著水流的作用會飄移,那麼就有可能飄移到其他的水域,清除浮動就類似於阻止這艘船飄移,比如安裝一道閘門,防止船飄進來或飄出去。透過設定特定的css屬性來清除浮動,就類似於這裡的安裝閘門。

建立BFC容器

之前在《對BFC的理解》中我們有提到過,可以透過建立BFC容器的方式來清除浮動,比如設定display: flow-root;,這就類似於在一個水域的下游處安裝閘門,防止這個水域內的船飄往下游。

假設有以下一段HTML:

<div style="width: 100px; height: 100px; border: 1px solid orange; float: left;">
    <span>文字文字111文字文字111文字文字111</span>
</div>
<div style="width: 100px; height: 100px; background: pink; ">
    <span>文字文字222</span>
</div>

在未清除浮動前是這樣的:

可以看到,文字111佔用了文字222所在div的容器,就像是船飄到了下游的水域,佔了文字222的位置。

將第一個div用BFC容器包裹後(類似於閘門的作用):

<div style="display: flow-root;">
    <div style="width: 100px; height: 100px; border: 1px solid orange; float: left;">
        <span>文字文字111文字文字111文字文字111</span>
    </div>
</div>
<div style="width: 100px; height: 100px; background: pink;">
    <span>文字文字222</span>
</div>

頁面就變成了下面這個樣子:

使用clear屬性

可以看到在規範文件中,還直接提供了一個clear屬性用於清除浮動。

This property indicates which sides of an element's box(es) may not be adjacent to an earlier floating box. The 'clear' property does not consider floats inside the element itself or in other block formatting contexts.

該屬性表示元素方框的哪些邊不得與先前的浮動方框相鄰。"清除"屬性不考慮元素本身內部或其他塊格式上下文中的浮動。

建立BFC的方式可以類比為上游水域在下游口安裝了閘門,使用clear屬性就可以類比為下游水域在上游口安裝閘門,防止上游的船飄進來。所以前面的例子中,我們也可以使用以下程式碼來清除浮動:

<div style="width: 100px; height: 100px; border: 1px solid orange; float: left;">
    <span>文字文字111文字文字111文字文字111</span>
</div>
<div style="width: 100px; height: 100px; background: pink; clear: left;">
    <span>文字文字222</span>
</div>

可以看到,最終效果和上面建立BFC的效果是一樣的。但實際我之前看到的解決浮動的方案中,比較推薦的做法是由浮動的元素這邊來處理清除,比如建立BFC容器,或者加在偽元素before或者after上。比如下面這段程式碼:

<div id="float-box">
    <div style="width: 100px; height: 100px; border: 1px solid orange; float: left;">
        <span>文字文字111文字文字111文字文字111</span>
    </div>
</div>
<style>
    #float-box::after {
        content: '';
        display: block;
        clear: left;
    }
</style>
<div style="width: 100px; height: 100px; background: pink;">
    <span>文字文字222</span>
</div>

上面的程式碼中使用::after元素建立了一個看不見的塊來清除浮動。

clear屬性可以設定多種值。

Name:clear
Value:none/left/right/both/inherit
Initial:none
Applies to:block-level elements
Inherited:no
Percentages:N/A
Media:visual
Computed value:as specified

根據文件裡的描述,left值表示當前塊不被相鄰的左浮動框影響,right值表示當前塊不被相鄰的右浮動框影響,both表示同時不受兩類浮動框的影響。

但是規範文件中也提示我們,使用clear屬性會產生副作用,使用none以外的值可能會引入間隙(clearance)。

Values other than 'none' potentially introduce clearance. Clearance inhibits margin collapsing and acts as spacing above the margin-top of an element. It is used to push the element vertically past the float.

“none”以外的值可能會引入間隙。 間隙會抑制邊距摺疊,並充當元素頂部邊距上方的間距。 它用於將元素垂直推過浮動。

在《對BFC的理解》中,我們提到過,在BFC容器中,相鄰塊級盒子之間的垂直'margin'會摺疊。這裡的意思應該就是指,設定了clear屬性為非none之後,會影響BFC容器裡的垂直margin摺疊。

文件中給出了間隙的值是怎麼計算得出的:

Then the amount of clearance is set to the greater of:

  1. The amount necessary to place the border edge of the block even with the bottom outer edge of the lowest float that is to be cleared.
  2. The amount necessary to place the top border edge of the block at its hypothetical position.

然後將間隙量設定為以下兩者中的較大值:

1.將塊的邊界邊緣與要清除的最低浮動的底部外邊緣對齊所需的量。
2.將塊的頂部邊框邊緣放置在其假設位置所需的量。

光從描述上看,有點抽象,尤其是第二種計算,這裡所謂的假設位置是哪裡呢?第一種似乎好理解一些,就是讓兩個邊緣對齊的一個間隔的量。所以文件中也舉了例子來配合解說。

  1. 示例1

從名稱就可以看出,F是個浮動塊高度為H,此時B1有個bottom margin值為M1,B2有個top margin值為M2,在B2未設定clear屬性之前,B1和B2之間的間距為M1和M2中的較大值,也就是產生了垂直margin的摺疊。

假設B1的底部邊框在y=0這個位置,就如上圖所示,此時浮動框F的頂部位置就在y=M1的位置,而B2的頂部邊框就在y=max(M1,M2)的位置,浮動框F的底部位置在y=M1+H的位置。

在這個例子中,B2不在F下面,這個例子所描述的就是需要新增間隙的場景。也就是說:

max(M1,M2) < M1 + H

根據文件中描述的計算方式,這裡需要計算兩次間隙量,C1和C2,然後取兩者中的較大值:C = max(C1, C2)

第一種方法是使 B2 的頂部與 F 的底部齊平,即 y(top of B2) = M1 + H。這意味著margin不再摺疊(B1和B2的間距肯定大於M1和M2),它們之間有了間隙:

此時它們的等式關係為:

F的底部 = B2的頂部邊框
M1 + H = M1 + C1 + M2
    C1 = M1 + H - M1 - M2
       = H - M2

第二中計算是保持 B2 的頂部位置,即 y(top of B2) = max(M1,M2)。也就是B2邊框在其假設位置,按照預期保持垂直margin摺疊的效果。此時的等式關係就是程式設計下面這樣了:

max(M1, M2) = M1 + C2 + M2
         C2 = max(M1, M2) - M1 - M2

因為假設了max(M1,M2) < M1 + H,因此可以得出以下不等式:

C2 = max(M1, M2) - M1 - M2 < M1 + H - M1 - M2 = H - M2
C2 < H1 - M2

又因為C1 = H1 - M2,所以在這個場景中C2 < C1。因此間隙量C=max(C1, C2)=C1。

  1. 示例2,負值間隙
<p style="margin-bottom: 4em">
            First paragraph.</p>

<p style="float: left; height: 2em; margin: 0">
    Floating paragraph.</p>

<p style="clear: left; margin-top: 3em">
    Last paragraph.</p>

在最後一個p元素未設定clear屬性之前,第一個和最後一個p元素之間的margin會摺疊,最後一個p元素的頂部邊框邊緣(top border edge)會與浮動p元素的頂部齊平。

當我們設定clear屬性用於清除浮動時,需要讓最後一個p元素的top border edge位於浮動框的下面,也就是說需要往下挪動2em。此時必須引入間隙,相應地,margin不再摺疊,此時我們來計算間隙量:

c + m-t = 2em
c = 2em - m-t = 2em - 3em = -1em

第一種方式計算C1 = H - M2 = 2em - 3em = -1em

第二種計算方式,也就是保持 最後一個P元素 的頂部位置,C2 = max(M1, M2) - M1 - M2 = 4em - 4em - 3em = -3em

所以最後C=max(C1,C2)=-1em。

從上面兩個例子可以看出,間隙量的兩種計算方式的區別就在於,是否改變浮動框後續元素的頂部位置。

總結

隨著彈性佈局、網格佈局等一系列新的佈局方式引入後,浮動的使用少了很多,但它仍然能實現特殊的網頁效果,因此我們還是需要對它進行必要的瞭解。

相關文章