目前在不考慮IE以及低端安卓機(4.3-)的兼容下,已經可以放心使用flex進行布局了。什么是flex布局以及它的好處,這里就不再贅述。
在這篇文章里,想說說flex布局的屬性語法及其細節。那么網上也有不少flex布局的教程,為什么又要再寫一篇?
首先,flex布局的迷之屬性們,如果一知半解,機械記憶的話,那不到半個月基本忘光光。先感受一下這12個flex布局屬性,是不是很“迷”人。
容器屬性
- flex-flow
- flex-direction
- flex-wrap
- justify-content
- align-items
- align-content
元素屬性
- order
- flex-grow
- flex-shrink
- flex-basis
- flex
- align-self
就連老外也都在twitter吐槽不好理解,可見還是有一定的學習成本。

而目前很多flex教程主要以列舉屬性為主,缺乏對比和理解性脈絡。
因此,下面會通過我梳理的一個脈絡去理解flex布局,包括不同屬性的異同以及一些容易造成誤解的細節點,徹底弄懂flex布局。
一、flex彈性盒模型
對於某個元素只要聲明了display: flex;,那么這個元素就成為了彈性容器,具有flex彈性布局的特性。

- 每個彈性容器都有兩根軸:主軸和交叉軸,兩軸之間成90度關系。注意:水平的不一定就是主軸。
- 每根軸都有起點和終點,這對於元素的對齊非常重要。
- 彈性容器中的所有子元素稱為<彈性元素>,彈性元素永遠沿主軸排列。
- 彈性元素也可以通過
display:flex設置為另一個彈性容器,形成嵌套關系。因此一個元素既可以是彈性容器也可以是彈性元素。
彈性容器的兩根軸非常重要,所有屬性都是作用於軸的。下面從軸入手,將所有flex布局屬性串起來理解。
二、主軸
flex布局是一種一維布局模型,一次只能處理一個維度(一行或者一列)上的元素布局,作為對比的是二維布局CSS Grid Layout,可以同時處理行和列上的布局。
也就是說,flex布局大部分的屬性都是作用於主軸的,在交叉軸上很多時候只能被動地變化。
1. 主軸的方向
我們可以在彈性容器上通過flex-direction修改主軸的方向。如果主軸方向修改了,那么:
- 交叉軸就會相應地旋轉90度。
- 彈性元素的排列方式也會發生改變,因為彈性元素永遠沿主軸排列。
flex-direction:row
flex-direction:column
flex-direction:row-reverse
flex-direction:column-reverse
2. 沿主軸的排列處理
彈性元素永遠沿主軸排列,那么如果主軸排不下,該如何處理?

通過設置flex-wrap: nowrap | wrap | wrap-reverse可使得主軸上的元素不折行、折行、反向折行。
默認是nowrap不折行,難道任由元素直接溢出容器嗎?當然不會,那么這里就涉及到元素的彈性伸縮應對,下面會講到。
wrap折行,顧名思義就是另起一行,那么折行之后行與行之間的間距(對齊)怎樣調整?這里又涉及到交叉軸上的多行對齊。
wrap-reverse反向折行,是從容器底部開始的折行,但每行元素之間的排列仍保留正向。

3. 一個復合屬性
flex-flow = flex-drection + flex-wrap
flex-flow相當於規定了flex布局的“工作流(flow)”
flex-flow: row nowrap;
三、元素如何彈性伸縮應對
當flex-wrap: nowrap;不折行時,容器寬度有剩余/不夠分,彈性元素們該怎么“彈性”地伸縮應對?
這里針對上面兩種場景,引入兩個屬性(需應用在彈性元素上)
flex-shrink:縮小比例(容器寬度<元素總寬度時如何收縮)flex-grow:放大比例(容器寬度>元素總寬度時如何伸展)
1. flex-shrink: 縮小比例
來看下以下場景,彈性容器#container寬度是200px,一共有三個彈性元素,寬度分別是50px、100px、120px。在不折行的情況下,此時容器寬度是明顯不夠分配的。
實際上,flex-shrink默認為1,也就是當不夠分配時,元素都將等比例縮小,占滿整個寬度,如下圖。

#container {
display: flex; flex-wrap: nowrap; }
元素收縮的計算方法
真的是等比縮小(每個元素各減去70/3的寬度)嗎?這里稍微深究一下它的收縮計算方法。
- 彈性元素1:50px→37.03px
- 彈性元素2:100px→74.08px
- 彈性元素3:120px→88.89px
先拋結論:flex-shrink: 1並非嚴格等比縮小,它還會考慮彈性元素本身的大小。
- 容器剩余寬度:
-70px - 縮小因子的分母:
1*50 + 1*100 + 1*120 = 270(1為各元素flex-shrink的值) - 元素1的縮小因子:
1*50/270 - 元素1的縮小寬度為縮小因子乘於容器剩余寬度:
1*50/270 * (-70) - 元素1最后則縮小為:
50px + (1*50/270 *(-70)) = 37.03px
加入彈性元素本身大小作為計算方法的考慮因素,主要是為了避免將一些本身寬度較小的元素在收縮之后寬度變為0的情況出現。
2. flex-grow: 放大比例
同樣,彈性容器#container寬度是200px,但此時只有兩個彈性元素,寬度分別是50px、100px。此時容器寬度是有剩余的。
那么剩余的寬度該怎樣分配?而flex-grow則決定了要不要分配以及各個分配多少。
(1)在flex布局中,容器剩余寬度默認是不進行分配的,也就是所有彈性元素的flex-grow都為0。

(2)通過指定flex-grow為大於零的值,實現容器剩余寬度的分配比例設置。

元素放大的計算方法
放大的計算方法並沒有與縮小一樣,將元素大小納入考慮。
僅僅按flex-grow聲明的份數算出每個需分配多少,疊加到原來的尺寸上。
- 容器剩余寬度:
50px - 分成每份:
50px / (3+2) = 10px - 元素1放大為:
50px + 3 * 10 = 80px
無多余寬度時,flex-grow無效
下圖中,彈性容器的寬度正好等於元素寬度總和,無多余寬度,此時無論flex-grow是什么值都不會生效。

同理,對於flex-shrink,在容器寬度有剩余時也是不會生效的。因此這兩個屬性是針對兩種不同場景的互斥屬性。
四、彈性處理與剛性尺寸
在進行彈性處理之余,其實有些場景我們更希望元素尺寸固定,不需要進行彈性調整。設置元素尺寸除了width和height以外,flex還提供了一個flex-basis屬性。
flex-basis設置的是元素在主軸上的初始尺寸,所謂的初始尺寸就是元素在flex-grow和flex-shrink生效前的尺寸。
1. 與width/height的區別
首先以width為例進行比較。看下下面的例子。#container {display:flex;}。
<div id="container"> <div>11111</div> <div>22222</div> </div>
(1) 兩者都為0

- width: 0 —— 完全沒顯示
- flex-basis: 0 —— 根據內容撐開寬度
(2) 兩者非0

- width: 非0;
- flex-basis: 非0
—— 數值相同時兩者等效
—— 同時設置,flex-basis優先級高
(3) flex-basis為auto

flex-basis為auto時,如設置了width則元素尺寸由width決定;沒有設置則由內容決定
(4) flex-basis == 主軸上的尺寸 != width

- 將主軸方向改為:上→下
- 此時主軸上的尺寸是元素的height
- flex-basis == height
2. 常用的復合屬性 flex
這個屬性應該是最容易迷糊的一個,下面揭開它的真面目。
flex = flex-grow + flex-shrink + flex-basis
復合屬性,前面說的三個屬性的簡寫。

一些簡寫
flex: 1=flex: 1 1 0%flex: 2=flex: 2 1 0%flex: auto=flex: 1 1 auto;flex: none=flex: 0 0 auto;// 常用於固定尺寸 不伸縮
flex:1 和 flex:auto 的區別
其實可以歸結於flex-basis:0和flex-basis:auto的區別。
flex-basis是指定初始尺寸,當設置為0時(絕對彈性元素),此時相當於告訴flex-grow和flex-shrink在伸縮的時候不需要考慮我的尺寸;相反當設置為auto時(相對彈性元素),此時則需要在伸縮時將元素尺寸納入考慮。
因此從下圖(轉自W3C)可以看到絕對彈性元素如果flex-grow值是一樣的話,那么他們的尺寸一定是一樣的。

五、容器內如何對齊
前面講完了元素大小關系之后,下面是另外一個重要議題——如何對齊。可以發現上面的所有屬性都是圍繞主軸進行設置的,但在對齊方面則不得不加入作用於交叉軸上。需要注意的是這些對齊屬性都是作用於容器上。
1. 主軸上的對齊方式
justify-content

2. 交叉軸上的對齊方式
主軸上比較好理解,重點是交叉軸上。因為交叉軸上存在單行和多行兩種情況。
交叉軸上的單行對齊
align-items
默認值是stretch,當元素沒有設置具體尺寸時會將容器在交叉軸方向撐滿。
當align-items不為stretch時,此時除了對齊方式會改變之外,元素在交叉軸方向上的尺寸將由內容或自身尺寸(寬高)決定。





注意,交叉軸不一定是從上往下,這點再次強調也不為過。

交叉軸上的多行對齊
還記得可以通過flex-wrap: wrap使得元素在一行放不下時進行換行。在這種場景下就會在交叉軸上出現多行,多行情況下,flex布局提供了align-content屬性設置對齊。
align-content與align-items比較類似,同時也比較容易迷糊。下面會將兩者對比着來看它們的異同。
首先明確一點:align-content只對多行元素有效,會以多行作為整體進行對齊,容器必須開啟換行。
align-content: stretch | flex-start | flex-end | center | space-between | space-around
align-items: stretch | flex-start | flex-end | center | baseline
在屬性值上,align-content比align-items多了兩個值:space-between和space-around。
align-content與align-items異同對比
與align-items一樣,align-content:默認值也是stretch。兩者同時都為stretch時,毫無懸念所有元素都是撐滿交叉軸。
#container {
align-items: stretch;
align-content: stretch;
}

當我們將align-items改為flex-start或者給彈性元素設置一個具體高度,此時效果是行與行之間形成了間距。
#container {
align-items: flex-start;
align-content: stretch;
}
/*或者*/ #container { align-content: stretch; } #container > div { height: 30px; }
為什么?因為align-content會以整行為單位,此時會將整行進行拉伸占滿交叉軸;而align-items設置了高度或者頂對齊,在不能用高度進行拉伸的情況下,選擇了用間距。

嘗試把align-content設置為頂對齊,此時以行為單位,整體高度通過內容撐開。
而align-items僅僅管一行,因此在只有第一個元素設置了高度的情況下,第一行的其他元素遵循align-items: stretch也被拉伸到了50px。而第二行則保持高度不變。
#container {
align-items: stretch;
align-content: flex-start;
}
#container > div:first-child {
height: 50px; }

兩者的區別還是不明顯?來看下面這個例子。
這里僅對第二個元素的高度進行設置,其他元素高度則仍保持內容撐開。

以第一個圖為例,會發現align-content會將所有行進行頂對齊,然后第一行由於第二個元素設置了較高的高度,因此體現出了底對齊。
兩者差異總結:
- 兩者“作用域”不同
- align-content管全局(所有行視為整體)
- align-items管單行
能否更靈活地設置交叉軸對齊
除了在容器上設置交叉軸對齊,還可以通過align-self單獨對某個元素設置交叉軸對齊方式。
- 值與
align-items相同 - 可覆蓋容器的
align-items屬性 - 默認值為
auto,表示繼承父元素的align-items屬性

#container {
display: flex; align-items: flex-start; } #container > div:first-child { align-self: stretch; } #container > div:nth-child(3) { align-self: center; } #container > div:nth-child(4) { align-self: flex-end; }
六、其他
order:更優雅地調整元素順序

#container > div:first-child {
order: 2; } #container > div:nth-child(2) { order: 4; } #container > div:nth-child(3) { order: 1; } #container > div:nth-child(4) { order: 3; }
order:可設置元素之間的排列順序
- 數值越小,越靠前,默認為0
- 值相同時,以dom中元素排列為准
七、總結



附
參考阮老師博文中的骰子練習,我做了張圖,大家不妨可以各自實現下,理解之后應該能夠比較輕松地寫出來。codepen

