想象一雙結實而富有彈性的大腿:理解 Flexbox 佈局

天方夜發表於2017-11-19

說明

本站不支援 CodePen 的指令碼插入,可以到我的部落格閱讀直接顯示示例程式碼的版本。

Flexbox 讓人困惑

有很多談及 Flexbox 的文章,但依然有不少前端對此感到困惑。一方面,flex 相關的 CSS 屬性繁多,影響到的具體效果也包含多個方面;另一方面,CSS 可以使用 Shorthand properties 風格的寫法(例如最常見的 background: url(images/bg.gif) no-repeat left top;),很容易讓新手弄不清具體含義。

這篇文章要講的 Flexbox 當然還是 CSS3 規範中的彈性盒模型,不過寫出前面一段,是因為我希望這篇文章可以解決那些問題——簡單說,就是 Flexbox 讓人困惑這樣的問題。解決的方法,就是理解它

什麼時候,我們會想到彈性

為了理解彈性盒模型,先要從古龍的筆法開始。古龍是臺灣的武俠小說大師,成就僅次於金庸。古龍描寫的一些人物,非常深入人心,其犀利以及令人難忘的程度,甚至超過金庸。比如,有一位中年男性,在整部小說裡,他的手非常重要,古龍對此有多次描寫——“他的手指修長而有力”。而在不止一本書中,不止一兩個情節中,有幾位青年女性,她們的腿非常重要,古龍的描述是——“大腿結實而富有彈性”,或者“修長結實而富有彈性”。

輕呼一口氣,思考一下,為什麼描寫手指的時候要說“修長而有力”,而描寫大腿卻是“結實而富有彈性”?這真是一個非常值得思考的問題。實際上,人的腿也可以是“修長而有力”的;而且,人的手指也是有彈性的。但古大師的描寫絕非隨意為之,其中的道理,可以隨便找些包含大腿的人物畫看一看,或者只是想象一下——一幅人物畫中,手指的面積有多大?除非為了強調手部而加了特技,否則手指所佔的面積是很小的;而大腿,在一幅正常的人物畫中,是充滿空間的。這裡說的充滿,當然並不是指完全佔滿空間,也不是上下左右一定沒有空隙。

記住這種“充滿”,現在思考一下 Flexbox。什麼時候,我們會想到或者說需要彈性這件事情?答案是當我們需要“充滿”一個容器的時候。帶著這種思考,再回到人物畫。手指只是在畫面的特定位置,解決這種問題,我們可以簡單地用 position: absolute;float: left; 這些屬性搞定。而大腿是“充滿”畫面的,當我們需要“充滿”容器的時候,彈性就很重要!要解決這類問題,我們應該思考的就不是某個區域性的空間,而是空間的分配。包括如何分配容器內的所有盒子,如果空間過大怎麼辦,如果空間過小怎麼辦;而在移動端,裝置的螢幕尺寸有很多種,問題就變成了空間有時候大、有時候小怎麼辦。

讀者應該已經能夠想到,彈性盒模型就是為了更方便地解決這些問題而產生的。那麼先看一看非彈性的盒子遇到這些場景會有哪些不便。

百分比網格

See the Pen understanding-css-flexbox 1: percentage by Alpha Bao (@AlphaBao) on CodePen.

上面是用浮動和百分比的方式寫的橫向網格,由於直接給其中一個設定了不同的高度,很明顯,可以看出,四個格子的高度是不同的。如果格子的高度變化是由其中的內容引起,也會存在同樣的問題。

另外,示例之中是四個格子,所以設定每個格子為 width: 25%; 就可以讓它們橫向充滿父級容器,而且大小變化也沒有影響。但如果需要渲染的資料是動態的,寫成具體某個百分比顯然就不行了。即元素個數變化時每個元素的百分比也需要變化,就需要修改 CSS。

這些問題都是因為這樣的盒模型是沒有“彈性”的,如果有彈性,就可以讓佈局按照我們希望的方式渲染。

Flex 網格

.container {
  display: flex;
}

彈性盒模型帶來了 Flexbox 佈局,像上面這樣,給充當 container 的盒子設定 display: flex; 就可以讓它的子元素彈性排列,預設是橫向的,因為 flex-direction: row; 是預設值,我們先不關心它。先看一下最常用的屬性 flex-grow

flex-grow

See the Pen understanding-css-flexbox 2: flex-grow by Alpha Bao (@AlphaBao) on CodePen.

可以看到其中一個是 flex-grow: 2;,其他都是 1,意思是這些子元素將充滿容器,它們將容器分成了若干份,每個 flex-grow: 1; 元素佔據一份,flex-grow: 2; 的佔兩份,因為它的 flex-grow 值是其他元素的兩倍。也就是說,flex-grow 決定子元素如何膨脹。在 Flexbox 的充滿/填充策略中,flex-grow 影響的是元素膨脹到多大。注意這其實是如何分配父級容器空間的問題,而且是容器大小會改變的情形下,所以具體子元素的大小是取決於空間剩餘情況的,並不是 flex-grow 越大,元素就一定會越大。

flex-basis

再看 flex-basis 屬性,它是指元素的初始大小,上一個例子中,只設定了 flex-grow,所以初始大小就是元素內容決定的,如果元素沒有內容,大小就是零。

See the Pen understanding-css-flexbox 3: flex-basis by Alpha Bao (@AlphaBao) on CodePen.

flex-basis 的預設值是 auto,是指元素的大小(本文中指的是元素橫向的長度,因為 flex-direction 預設值是 row,這決定了 main axis(主軸)是橫向的,即容器的子元素橫向排列)根據元素的長度屬性或者由內容決定。可以是具體長度值也可以是百分比。

.c3 {
  width: 15em;
  flex-basis: auto;
}

此時初始寬度是 15em,也可以寫為下面這樣:

.c3 {
  flex-basis: 15em;
}

兩種寫法效果相同。

計算完初始大小,再根據容器空間剩餘情況,繼續完成“充滿”容器這件事情。如果先只考慮空間還有剩餘的情況,前面提到的 flex-grow 屬性就開始起作用,使元素膨脹,直到充滿容器。

flex-shrink

前面考慮的都是空間還有剩餘的情況,接下來考慮一下空間不足的情況。首先要弄清楚,具體怎樣會導致空間不足。

See the Pen understanding-css-flexbox 4: flex-shrink by Alpha Bao (@AlphaBao) on CodePen.

可以看到,通過設定寬度或者由內容填充,此時可能導致空間不足。此時 flex-shrink 會起作用。首先計算初始大小,再考慮空間不足的情況,這時候根據 flex-shrink 的值決定如何收縮各個元素,數值越大,相對其他元素的收縮倍數就越大。flex-shrink 預設值是 1,即不改變這個屬性值的情況下,空間不足時每個元素的收縮程度相同。如果改為 0,則不收縮。

flex

flex 是上面三者的簡寫形式(這類寫法的屬性就是 Shorthand properties)。

.item {
  flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
}

See the Pen understanding-css-flexbox 5: flex by Alpha Bao (@AlphaBao) on CodePen.

上面的每個子元素都設定了 flex: 1 1 100px;,即初始寬度值是 100px,如果空間剩餘,每個元素平均分配,如果空間不足,每個元素同等程度的收縮。flex: 0 1 auto;flex 屬性的預設值,表示不膨脹(如果都不膨脹,那麼有剩餘空間也不會充滿),收縮因數是 1,元素在主軸方向上的長度取決於長度值或者元素內容。

通過上面幾個例子,可以看得出在大小會動態變化的父級容器裡面,這種分配空間的策略優勢很明顯,這就是彈性帶來的便利。flex 是實際開發中常用的寫法,它的內容其實就是如何“充滿”容器空間的策略,是 Flexbox 中最重要的部分。

結束語

本文只談及了 flex-grow flex-shrink flex-basis,關於 Flexbox,還有很多內容。比如決定纏繞方式的 flex-wrap,它的效果類似 float,還有 justify-content,它決定元素在 main axis(主軸)方向的對齊方式,align-self 則決定 cross axis(垂直的交叉軸)方向(在例如主軸是水平方向,而各個元素具有不同的高度這類情況下起作用)。屬性名有點亂,不過它們都是圍繞充滿彈性擴充套件開的。只要結合對“充滿”空間這件事情的想象,理解了彈性的含義,就弄清了目標與方法,應該能比較容易地學會並運用 Flexbox 了。

如上所見,本文所談的,並不是大腿這件事情,如果你想看“真正的”大腿,可以讀一讀古龍的《多情劍客無情劍》、《午夜蘭花》、《長生劍》以及《蕭十一郎》。

擴充套件閱讀

  1. A Complete Guide to Flexbox
  2. A Visual Guide to CSS3 Flexbox Properties

相關文章