flex-grow 不易理解,難道不是嗎?
來自: http://jinlong.github.io/2016/02/04/flex-grow-is-weird/
當我剛接觸 flex-grow 時,為了探尋它的工作原理,做了一個 簡單的例子 。
我以為理解的挺透徹了,但是當我把它應用到同事的網站上時,效果跟我想象的完全不同。無論怎么改,布局都無法像我的demo那樣展示。這時我才意識到,我并沒有完全掌握 flex-grow 。
flex-grow 為何不正常
在我深入剖析 flex-grow 的功能之前,我想解釋一下我起初犯了什么錯。
我認為所有 flex 元素的 flex-grow 如果設置為 1 ,它們將一樣寬。如果某一項的 flex-grow 設置為 2 ,它將是其它元素的二倍寬。
一切聽起來順理成章。我 上面的例子 貌似也印證了這點。父元素是900px寬, flex-grow: 2 的 section 元素計算后是600px寬, flex-grow: 1 的 aside 元素計算后是300px寬。
如你所見,它在這個例子中展現的近乎完美,可是在真實的例子中卻不盡人意,即使我們用了完全相同的 CSS。事實證明,問題不在 CSS,而在于內容(或者說缺乏內容)。我的測試用例只用了兩個空元素,無法展示這個屬性最重要的細節。
flex-grow 到底如何工作
啰嗦了半天,我終于要解釋 flex-grow 沒有盡如人意的原因了。
為了闡明原因,我又搞了個 栗子 ,所有的設置跟 第一個栗子 完全一致,只不過 section 和 aside 元素不再是空的。看吧,兩個元素的比例不再是 2 : 1,flex-grow 為 1 的元素的確比 flex-grow 為 2 的元素寬不少吶。
解析
如果給父元素設置了 display: flex; ,子元素僅僅是水平排列,沒有其它效果了。如果沒有足夠的空間,它們會收縮一些尺寸。另一方面,如果有足夠的空間,它們也不會擴展,因為 Flexbox 希望我們自己定義擴展多少。 flex-grow 恰恰用來定義剩余空間如何分配,每一項分享多大寬度。
換言之:
flex 容器為它的子元素分配剩余空間(它們的擴展系數是成比例的),從而填滿整個容器,或者收縮元素(它們的收縮系數也是成比例的),從而阻止溢出。
https://drafts.csswg.org/css-flexbox/#flexibility
證明
如果我們可視化一把,概念就很清晰明了了。
首先,我們給父元素設置了 display: flex ,然后它的子元素成了 flex 元素,一個挨一個的橫向排列。
下一步,我們要決定每個元素能獲得多少剩余空間。先前的例子中,第一個元素獲得了 2/3 的剩余空間( flex-grow: 2 ),第二個元素獲得了 1/3 的剩余空間( flex-grow: 1 )。想知道 flex-grow 總共的值是多少,看看剩余空間被分成了幾份吧。
最終我們得出了可分配的塊數,根據 flex-grow 的值,每個元素可以獲得適當的塊數。
計算結果
理論和視覺展示都不錯,讓我們為 上面例子 做點算術吧。
我們需要4組數字:父容器寬度, section 和 aside 元素的初始寬度,以及 flex-grow 的總值。
父容器寬: 900px
section 寬: 99px
aside 寬: 623px
flex-grow 總值: 3
1. 首先計算剩余空間
父容器寬減去每一個子元素的初始寬度。
900 - 99 - 623 = 178
</div>
父容器寬 - section 寬 - aside 寬 = 剩余空間
2. 然后計算 flex-grow 的1份是多寬
既然有了剩余空間,我們還需要確定把它切成幾份。重要的是,我們不按元素的個數切分剩余空間,而是按 flex-grow 總值,所以這里是 3 ( flex-grow: 2 + flex-grow: 1 )
178 / 3 = 59.33
</div>
剩余空間 / flex-grow 總值 = “1份 flex-grow 的寬”
3. 最終所有的元素瓜分剩余空間
依據它們的 flex-grow 值,section 需要 2 份(2 59.33),aside 需要 1 份(1 59.33)。這個數字再與每個元素的初始寬度相加。
99 + (2 * 59.33) = 217.66 (≈218px)
</div>
初始 section 寬度 + (section 的 flex-grow 值 * “1 份 flex-grow 的寬”) = 新的寬度
623 + (1 * 59.33) = 682.33 (≈682px)
</div>
初始 aside 寬度 + (aside 的 flex-grow 值 * “1 份 flex-grow 的寬”) = 新的寬度
so easy,不是嗎?
好吧,那為什么第一個例子正常呢?
我們按已有的公式,算下 第一個例子 。
父容器寬: 900px
section 寬: 0px
aside 寬: 0px
flex-grow 總值: 3
1. 計算剩余空間
900 - 0 - 0 = 900
</div>
2. 計算 flex-grow 的1份是多寬
900 / 3 = 300
</div>
3. 分配剩余空間
0 + (2 * 300) = 600 0 + (1 * 300) = 300
</div>
如果每個元素的寬是 0,剩余空間等于父容器的寬度,因此,看起來像是 flex-grow 按比例劃分了父容器的寬度。
flex-grow 和 flex-basis
快速回顧一下:剩余空間被 flex-grow 的總值劃分,由此產生的商,乘以各自的 flex-grow 值,結果再加上每個元素的初始寬度。
但是如果沒有剩余空間或者不想依賴元素的初始寬度,我們可以設置它嗎?還能用 flex-grow 嗎?
當然可以。有個 flex-basis 屬性,它可以定義元素的初始寬度。如果 flex-basis 和 flex-grow 一起設置,寬度的計算方式得變一下。
<‘flex-basis’> :按 flex 因子分配剩余空間之前,每個 flex 元素的最初主要尺寸。
https://drafts.csswg.org/css-flexbox/#valdef-flex-flex-basis
如果給某個元素設置了 flex-basis 屬性,我們計算的時候就不能再用元素本身的初始寬度了,而要用 flex-basis 屬性的值。
我調整了一下 先前的例子 ,給每個元素加了 flex-basis 屬性。
父容器寬: 900px
section 寬: 400px (flex-basis 值)
aside 寬: 200px (flex-basis 值)
flex-grow 總值: 3
1. 計算剩余空間
900 - 400 - 200 = 300
</div>
2. 計算 flex-grow 的1份是多寬
300 / 3 = 100
</div>
3. 分配剩余空間
400 + (2 * 100) = 600 200 + (1 * 100) = 300
</div>
僅僅是為了完整性,我才用到 px 值,用 百分比的話當然也沒問題 。
與盒模型結合
為了覆蓋所有情況,如果我們加上 padding 和 margin 看看會發生什么, 啥也沒發生 ,第一步計算的時候,只要減去 margin 就好了。
唯一需要注意的是,使用 box-sizing 的話, flex-basis 跟 width 屬性表現相似。 如果 box-sizing 屬性改變 ,計算結果也會變化。如果 box-sizing 設置為 border-box ,計算時只需用到 flex-basis 和 margin 值,因為 padding 已經包含到寬度里面了。
更多實用的例子
好吧,算術是玩夠了。我再展示一些項目中合理使用 flex-grow 的例子吧。
不限寬度:[ x ]%
實際應用中,剩余空間是自動分配的,如果想讓子元素填滿父容器的話,我們沒必要再考慮寬度值。
See the Pen flex-grow by Manuel Matuzovic ( @matuzo ) on CodePen .
固定寬度的 “圣杯” 3列流式布局
固定加自適應寬度的混合布局,可以用浮動實現,但是既不簡單直觀,又不靈活。Flexbox 實現的話,加點兒 flex-grow 和 flex-basis 魔法,簡直小菜一碟。
See the Pen Layout using fluid and fixed widths by Manuel Matuzovic ( @matuzo ) on CodePen .
使用任何元素填滿剩余空間
比如,一個 label 元素后面緊跟著輸入框,想讓輸入框填滿剩余空間,不再需要丑陋的 hacks 。
See the Pen Filling up the remaining space in a form by Manuel Matuzovic ( @matuzo ) on CodePen .
Philip Waltons 的Solved by Flexbox 一文可以找到更多示例。
聽聽標準規范怎么說
根據標準,使用 flex 的簡寫,比直接用 flex-grow 更好。
Authors are encouraged to control flexibility using the flex shorthand rather than flex-grow directly, as the shorthand correctly resets any unspecified components to accommodate common uses.
https://drafts.csswg.org/css-flexbox/#flex-grow-property
但是小心!如果僅僅使用 flex: 1; ,以上某些例子可能無法正常展示。
我們的例子要用 flex 的話,應該這么定義:
flex: 2 1 auto; /* (<flex-grow> | <flex-shrink> | <flex-basis>) */
</div>
深入學習 Flexbox
如果你想深入學習 Flexbox,可以看看這些不錯的資源:
- A Complete Guide to Flexbox by Chris Coyier
- Flexbox adventures by Chris Wright
- Flexbox Froggy by Thomas Park
- What the Flexbox? by Wes Bos
- flexboxin5
- Flexbox Tester by Mike Riethmuller
總結經驗教訓
flex-grow 不易理解嗎?也不全是。我們只需理解它如何工作,它做了什么。如果一個元素設置 flex-grow 為 3 ,并不代表它是 flex-grow 為 1 的元素的3倍大,準確含義是:它的初始寬度可以增加的像素值是另一個元素的3倍。
我通過 兩個空元素 測試 flex-grow ,得到的結論跟 真實情況 完全不符。應該在盡可能真實的環境中驗證新事物,這樣才能得到最切合實際的結論。
</div>