Flexbox 是一種 CSS 布局機制,可以說是目前瀏覽器原生支持的最好、使用最廣泛的布局機制了。本文通過一些例子來說明 Flexbox 布局的工作原理,可以讓我們更好的使用 Flexbox。
與 CSS Grid 可以同時在橫向和縱向兩個方向進行布局不同,Flexbox 只能在單一方向上進行布局,即要么橫向,要么縱向。所謂布局,其實就是空間的分配過程,也就是說計算元素尺寸和容器剩余空間尺寸的過程。
Flexbox 的布局原理
整個布局過程我們可以簡單的總結如下:
- 計算 flex 容器內的可用空間。整個容器的尺寸減去容器的 border、padding 等所得的剩余空間尺寸。
- 計算每個 flex 元素的 flex base 尺寸和元素的假設尺寸。具體計算方法是取 flex-basis、min-width 和 flex 元素內容尺寸的較大者。flex base 尺寸是 flex 元素需要的最小尺寸,這個尺寸不能小於元素內容的尺寸。元素的假設尺寸是指在 flex 因子生效前元素的尺寸,flex 因子生效后可能導致元素發生伸縮。
- 計算容器內所有 flex 元素的假設尺寸總和。
- 將所有元素的假設尺寸總和與容器內可用空間尺寸做比較,來確定 flex 因子,也就是說當假設尺寸總和超過容器內可用空間尺寸時,使用 flex-shrink,否則使用 flex-grow。在同一時間,flex-shrink 和 flex-grow 只有一個生效。
所謂 flex 因子,簡單來說就是縮小和放大。瀏覽器在進行 flexbox 布局時會先確定使用哪種 flex 因子,然后再根據選用的 flex 因子來對元素尺寸進行調整。
在進行調整的時候,就會涉及到一個剩余空間的計算問題。如果 flex 元素明確指定了尺寸大小(definite size,比如設置了 width),那么這個元素就是不可伸縮的。如果沒有顯式指定尺寸,則會按照上面第2步那樣計算假設尺寸。剩余空間的尺寸就是容器內的可用空間尺寸減去這些元素的尺寸之和。
例子說明
我們有如下 dom 結構:
<div id="flex">
<div id="a">Antidisestablishmentarianism</div>
<div id="b">B</div>
<div id="c">Cherries jubilee</div>
<div id="d">D</div>
<div id="e">E</div>
</div>
樣式如下:
[id=flex] {
font-weight: 300;
display: flex;
outline: 1px dashed #555;
width: 1200px;
margin: 3rem auto;
}
/*其他樣式已省略*/
頁面展示效果如下:
我們沒有設置元素的 flex
樣式屬性,默認值是 0 1 auto
。flex
樣式屬性是 flex-grow
、flex-shrink
和 flex-basis
這三個樣式屬性的簡寫形式。0 1 auto
分別對應為 flex-grow
、flex-shrink
和 flex-basis
的值。
通過取值可以看到,因為我們禁止了放大和收縮,並且 flex-basis
的值是 auto
,瀏覽器就使用元素的最大內容尺寸來計算所有元素的尺寸總和,比容器的尺寸(1200px)小,所有會有額外的剩余空間。
關於 flex
這個樣式屬性,我們額外做一些說明。flex
屬性可以接收最少一個、最多三個屬性值。
當只有一個屬性值的時候,flex
的工作模式是這樣的 <number> 1 0
。即 flex: 2
最終的結果是 flex: 2 1 0
。
當有兩個屬性值的時候,第一個值會被解析為 flex-grow
,第二個值如果是數字,則會被解析為 flex-shrink
,如果是一個合法的寬度值則會被解析為 flex-basis
。即 flex: 1 0
解析為 flex: 1 0 0
,而 flex: 1 20rem
則被解析為 flex: 1 1 20rem
。
當有三個屬性值的時候,第一個值被解析為 flex-grow
,第二個值必須為數字,且會被解析為 flex-shrink
,第三個值則必須是合法的寬度值,被解析為 flex-basis
。
設置 flex-grow
我們增加如下樣式設置:
[id=flex] > div {
flex: 1;
}
實際上等同於 flex: 1 1 0
,即 flex-grow: 1
。從前面的例子我們可以看到,所有元素的假設尺寸之和是小於容器剩余空間尺寸的,所以瀏覽器會使用 flex-grow
來作為 flex 因子,因為我們設置了 flex-grow: 1
,因此瀏覽器會等比放大所有的元素。如下圖:
實際上瀏覽器會循環通過下面的公式來計算每個元素的最終尺寸:
當前元素伸縮值 = (當前剩余空間 - 所有剩余元素的 flex-grow 值的和) * 當前元素的 flex-grow 值
在上面的例子中,容器的剩余空間為 1200px,因此,通過公式計算:
( 1200 ÷ ( 1 + 1 + 1 + 1 + 1 ) ) × 1 = 240
元素 A 的最終尺寸為 240px + flex-basis = 240px + 0 = 240px
。但是,因為 Antidisestablishmentarianism
這個單詞比較長,實際會占用 417px
的空間大小,因此元素 A 的最終尺寸為 417px
。
此時,在計算元素 B 的尺寸時,剩余空間為 1200px - 417px = 783px
。元素 B 的伸縮值為:
( 783 ÷ ( 1 + 1 + 1 + 1 ) ) × 1 = 195.75 0 + 195.75 = 195.75
同理,元素 C、D、E 的伸縮值分別為:
C: ( 587 ÷ ( 1 + 1 + 1 ) ) × 1 ) = 195.67
D: ( 391.33 ÷ ( 1 + 1 ) ) × 1 = 195.665
E: ( 391 - 195.665 ÷ 1 ) × 1 = 195.335
總結來說,瀏覽器從剩余空間中減去已經分配的空間,然后計算下一個 flex 元素的伸縮值和最終大小。
差異化的 flex-grow
我們修改一下元素的 flex-grow
值:
div > :not([id=c]) {
flex: 1;
}
[id=c] {
flex: 5;
}
此時,頁面展示效果如下:
各元素的伸縮值計算如下:
A: 417px
B: ( 783 ÷ ( 1 + 1 + 5 + 1 ) ) × 1 = 98
C: ( 685 ÷ ( 5 + 1 + 1 ) ) × 5 = 490
D: ( 195 ÷ ( 1 + 1 ) ) × 1 = 97.5
E: ( 97.5 ÷ 1 ) × 1 = 97.5
設置 flex-basis: auto
前面的例子里,我們都設置了 flex-basis
的值為 0,現在我們設置 flex-basis: auto
,再來看看元素尺寸是如何計算的。
刪除其他元素的 flex 指定,修改樣式如下:
[id=c] {
flex: 5;
}
此時等價於其他元素是 flex: 0 1 auto
,元素 C 是 flex: 5 1 0
。展示效果如下:
在前面的例子中,所有元素都是 flex-basis: 0
,現在設置為 A、B、D、E 都設置成了 flex-basis: auto
。因此,元素 A、B、D、E 都會使用元素內容的尺寸來計算。
因此,剩余剩余可分配空間大小為:
1200 - ( 417 + 33 + 35 + 30 ) = 685
因為元素 C 是唯一可伸縮的元素,因此它的伸縮值為 685。
設置 flex-shrink
現在我們做一些調整,讓所有 flex 元素的尺寸總和大於容器的可用空間尺寸,使得 flex-shrink 生效。
樣式調整如下:
:not([id=a]) {
flex-shrink: 1;
}
[id=a] {
flex-shrink: 5;
}
div > div {
flex-basis: 500px;
}
可以看到,所有元素 flex-grow: 0
,元素 A flex-shrink: 5
,其他元素 flex-shrink: 1
,所有元素總尺寸 2500px。
顯示效果如下:
因為元素尺寸總和超過了容器可用尺寸(1200px),因此 flex-shrink 將會生效。
元素 A 的伸縮值為:
( 1300 ÷ ( 5 + 1 + 1 + 1 + 1 ) ) × 5 = 722.22
則元素 A 的實際尺寸計算為:
500 - 722.22 = -222.22
可以看到得出的是負值。如果元素 A 是空元素,那么最終尺寸會是零。本例中,元素 A 的尺寸就是起內容的尺寸,大概 34px。
確定了元素 A 的尺寸之后,容器剩余空間尺寸為 1200 - 34 = 1166
。其他元素的伸縮值計算為:
B: ( 1166 ÷ ( 1 + 1 + 1 + 1 ) ) × 1 = 291.5
C: ( 874.5 ÷ ( 1 + 1 + 1 ) ) × 1 = 291.5
D: ( 583 ÷ ( 1 + 1 ) ) × 1 = 291.5
E: ( 291.5 ÷ 1 ) × 1 = 291.5
值得注意的一點是,如果 flex-grow
和 flex-shrink
的取值都為 0,那么元素即不會放大也不會縮小,當所有元素的尺寸總和超過容器空間之后,就會產生溢出效果。
設置 flex-wrap
當所有元素的尺寸總和超過容器空間之后,就會產生溢出效果。我們可以通過設置 flex-wrap: wrap
來是超出的元素換行。
我們調整下樣式:
div > div {
flex-basis: 500px;
}
[id=flex] {
flex-wrap: wrap;
}
顯示效果如下:
我們可以看到,由於 flex-basis: 500px
且默認的 flex-grow: 0
,因此每一行的末尾有 200px 的剩余空間。
我們可以通過設置 flex-grow: 1
來讓元素占滿剩余空間。
div > div {
flex: 1 1 500px;
}
此時顯示效果如下:
可以看到,每行的剩余空間都被占滿了。
總結
Flexbox 布局有時候會有一些復雜和難以理解。在實際使用過程中,我們需要牢記如下幾點:
- Flexbox 只在單一方向上分配空間,行或者列。
- flex-basis 定義了元素的最小尺寸,有時候元素的內容尺寸可能會比 flex-basis 定義的尺寸大。
- 瀏覽器同時只會使用 flex-grow 或者 flex-shrink 來排列元素,不會同時使用。
- 實際使用 flex-grow 還是 flex-shrink 取決於元素尺寸總和與容器剩余空間的大小。
- 瀏覽器根據 flex 因子以及元素本身設置的系數來分配每個元素的空間。
關注微信公眾號,獲取最新推送~
加微信,深入交流~