深入理解 flex-grow、flex-shrink、flex-basis

蘑菇街前端團隊發表於2019-12-09

1. Flex佈局

Flex 是 Flexible Box 的縮寫,意為"彈性佈局",用來為盒狀模型提供最大的靈活性 flex屬性是flex-grow, flex-shrink 和 flex-basis的簡寫,預設值為 0 1 auto

任何一個容器都可以用 Flex 進行佈局(如果不會 flex 佈局的可見阮老師的 Flex 佈局教程),而且 Flex 是發生在父容器和子容器之間的佈局關係的,那麼父容器與子容器的關係是怎麼樣子的,又是怎麼計運算元容器所佔用的空間的呢,怎麼進行彈性佈局的呢?

欲要解決上面的問題,首先得了解 flex-grow 和 flex-shrink 是怎麼計算的?flex-basis 和 width 又有什麼關係和區別?

接下來,我們先提出兩個概念:剩餘空間和溢位空間,具體是什麼意思我們後面慢慢解釋。

2. flex-grow

flex-grow屬性在MDN上的定義是:

定義彈性盒子項(flex-item)的拉伸因子,預設值0”

傳統的佈局是子容器在父容器中從左到右進行佈局,應用 flex 進行佈局,那麼父容器一定設定 display: flex,子容器要“佔有”並且“瓜分”父容器的空間,如何佔有、瓜分的策略就是彈性佈局的策略。這裡就要解釋到“剩餘空間”的概念:

子容器在父容器的“主軸”上還有多少空間可以“瓜分”,這個可以被“瓜分”的空間就叫做剩餘空間。

文字總是很抽象,舉個例子就能理解剩餘空間了,現在有如下的程式碼:

HTML 程式碼:

<div class="container">
    <div class="item a">
      <p>A</p>
      <p> width:100</p>
    </div>
    <div class="item b">
      <p>B</p>
      <p> width:150</p>
    </div>
    <div class="item c">
      <p>C</p>
      <p> width:100</p>
    </div>
</div>
複製程式碼

CSS程式碼:

.container {
    margin:10px;
    display: flex;
    width: 500px;
    height: 200px;
    background-color: #eee;
    color: #666;
    text-align: center;
}
.item {
    height: 100px;
}
.item p {
  margin: 0;
}
.a{
    width: 100px;
    background-color:#ff4466;
}
.b{
    width: 150px;
    background-color:#42b983;
}
.c{
    width: 100px;
    background-color:#61dafb;
}
複製程式碼

展示的效果如下(最後那個框是截圖的時候的標註,不是展示出來的效果):

image-20191129122900800

一圖勝千言,看到這個圖應該就明白什麼是剩餘空間了。

父容器的主軸還有這麼多剩餘空間,子容器有什麼辦法將這些剩餘空間瓜分來實現彈性的效果呢?

這就需要用到flex-grow 屬性了,flex-grow 定義子容器的瓜分剩餘空間的比例,預設為 0,即如果存在剩餘空間,也不會去瓜分。

flex-grow例子,將上面的例子改成如下程式碼:

HTML 程式碼(程式碼只增加了 flex-grow 的說明,沒有其他結構的變動):

<div class="container">
    <div class="item a">
      <p>A</p>
      <p> width:100</p>
      <p>flex-grow:1</p>
    </div>
    <div class="item b">
      <p>B</p>
      <p> width:150</p>
      <p>flex-grow:2</p>
    </div>
    <div class="item c">
      <p>C</p>
      <p> width:100</p>
      <p>flex-grow:3</p>
    </div>
</div>
複製程式碼

CSS 程式碼(給每個子容器增加了 flex-grow):

.container {
    margin:10px;
    display: flex;
    width: 500px;
    height: 200px;
    background-color: #eee;
    color: #666;
    text-align: center;
}
.item {
    height: 100px;
    p {
      margin: 0;
    }
}
.a{
    width: 100px;
    flex-grow:1;
    background-color:#ff4466;
}
.b{
    width: 150px;
    flex-grow:2;
    background-color:#42b983;
}
.c{
    width: 100px;
    flex-grow:3;
    background-color:#61dafb;
}
複製程式碼
結果如下:

深入理解 flex-grow、flex-shrink、flex-basis

最初,我們發現,子容器的寬度總和只有 350px,父容器寬度為 500px,那麼剩餘空間就出現了,為 150px。當設定了 flex-grow 之後, A,B,C三個子容器會根據自身的 flex-grow 去“瓜分”剩餘空間。

在這裡我們總結為 flex-grow 屬性決定了子容器要佔用父容器多少剩餘空間。

計算方式如下:
  • 剩餘空間:x
  • 假設有三個flex item元素,flex-grow 的值分別為a, b, c
  • 每個元素可以分配的剩餘空間為: a/(a+b+c) * x,b/(a+b+c) * x,c/(a+b+c) * x

以 A 為例子進行說明: A 佔比剩餘空間:1/(1+2+3) = 1/6,那麼 A “瓜分”到的 150*1/6=25,實際寬度為100+25=125

考慮是否可以把 flex-grow 設定的值小於 1,而且 flex-grow 的和也小於 1 呢?只要把上面公式的分母(flex-grow 的和)設定為 1 就好啦!

3. flex-shrink

說完 flex-grow,我們知道了子容器設定了 flex-grow 有可能會被拉伸。那麼什麼情況下子容器被壓縮呢?考慮一種情況:如果子容器寬度超過父容器寬度,即使是設定了 flex-grow,但是由於沒有剩餘空間,就分配不到剩餘空間了。這時候有兩個辦法:換行和壓縮。由於 flex 預設不換行,那麼壓縮的話,怎麼壓縮呢,壓縮多少?此時就需要用到 flex-shrink 屬性了。

flex-shrink屬性在MDN上的定義是:

指定了 flex 元素的收縮規則,預設值是 1

此時,剩餘空間的概念就轉化成了“溢位空間”

計算方式:
  • 三個flex item元素的width: w1, w2, w3
  • 三個flex item元素的flex-shrink:a, b, c
  • 計算總壓縮權重: sum = a * w1 + b * w2 + c * w3
  • 計算每個元素壓縮率: S1 = a * w1 / sum,S2 =b * w2 / sum,S3 =c * w3 / sum
  • 計算每個元素寬度:width - 壓縮率 * 溢位空間
舉例說明:
<div class="container">
   <div class="item a">
     <p>A</p>
     <p> width:300</p>
     <p>flex-shrink: 1</p>
   </div>
   <div class="item b">
     <p>B</p>
     <p> width:150</p>
     <p>flex-shrink: 2</p>
   </div>
   <div class="item c">
     <p>C</p>
     <p> width:200</p>
     <p>flex-shrink: 3</p>
   </div>
</div>
複製程式碼
.container {
   	margin:10px;
   	display: flex;
   	width: 500px;
   	height: 200px;
   	background-color: #eee;
   	color: #666;
   	text-align:center;
}
.item {
		height: 100px;
}
.item p {
		margin: 0;
}
.a{
   	width: 300px;
   	flex-grow: 1;
   	flex-shrink: 1;
   	background-color:#ff4466;
}
.b{
   	width: 150px;
   	flex-shrink: 2;
   	background-color:#42b983;
}
.c{
   	width: 200px;
   	flex-shrink: 3;
   	background-color:#61dafb;
}
複製程式碼

子容器寬度總和為650,溢位空間為150 總壓縮:300 * 1 + 150 * 2 + 200 * 3 = 1200 A的壓縮率:300*1 / 1200 = 0.25 A的壓縮值:150 * 0.25 = 37.5 A的實際寬度:300 - 37.5 = 262.5

結果如下:

深入理解 flex-grow、flex-shrink、flex-basis

同樣,如果出現flex-shrink總和小於1?那麼計算溢位空間(收縮總和)的結果有所變化。比如:shrink設定為0.1, 0.2, 0.3, 原溢位空間為200,實際溢位空間:200 * (0.1 + 0.2 + 0.3)/ 1 = 120。

注意:如果子容器沒有超出父容器,設定 flex-shrink 無效

4. flex-basis

MDN定義:指定了 flex 元素在主軸方向上的初始大小

一旦 flex item 放進 flex 容器,並不能保證能夠按照 flex-basis 設定的大小展示。瀏覽器會根據 flex-basis 計算主軸是否有剩餘空間。既然是跟寬度相關,那麼 max-width,min-width,width 和 box 的大小優先順序是怎麼樣的。

舉例說明:
<div class="container">
    <div class="item a">A</div>
    <div class="item b">B</div>
    <div class="item c">C</div>
</div>
複製程式碼
.container {
    margin:10px;
    display: flex;
    width: 500px;
    height: 200px;
    background-color: #eee;
    text-align: center;
    line-height: 100px;
    color: #666;
}
.item {
    width: 100px;
    height: 100px;
}
.a{
    flex-basis: 200px;
    background-color:#ff4466;
}
.b{
    max-width: 50px;
    flex-basis: 150px;
    background-color:#42b983;
}
.c{
    background-color:#61dafb;
}
複製程式碼
結果如下:

深入理解 flex-grow、flex-shrink、flex-basis
上面的例子可以通過最終元素的寬度看出幾個屬性的優先順序關係:

max-width/min-width > flex-basis > width > box

5. 應用場景

  1. 一種很常見的佈局:當內容區域高度不夠的時候,footer仍然需要固定在底部。這時候,我們可以給main使用flex-grow: 1,使它自動填滿剩餘空間。

深入理解 flex-grow、flex-shrink、flex-basis

2 . 在我們開發一種常見的表單元件的時候,使用flex佈局,可以使輸入框佔滿剩餘空間。

深入理解 flex-grow、flex-shrink、flex-basis

而大部分場景下我們不希望元素被壓縮,所以flex-shrink通常設定為0。

6. 總結

最後,我們需要注意的是:

  • flex items 總和超出 flex 容器,會根據 flex-shrink 的設定進行壓縮
  • 如果有剩餘空間,如果設定 flex-grow,子容器的實際寬度跟 flex-grow 的設定相關。如果沒有設定flex-grow,則按照 flex-basis 展示實際寬度

參考文獻: