css flex佈局 精確計算成員寬度值

changli2018發表於2019-05-10

前言

css 的 flex 彈性佈局,廣泛應用在當今的頁面開發中,其彈性伸縮的的靈活性非常擅長於開發有響應式需求的網站。但是有很多時候,其靈活性也會讓人疑惑,特別是確定尺寸時,基本上是靠不斷嘗試。為了在開發時更加得心應手,下面將以主軸方向上,也就是預設佈局方向上的成員寬度值計算為例子進行研究。

準備

flex 是彈性的伸縮佈局,具有伸和縮兩個特性,下面在研究時也以伸和縮兩種狀態來進行研究。 在開始之前,先說一下一些定義,類名 container 是 flex 容器, 類名 item 是 flex 的成員物件,每個成員物件內嵌了自已的內容。每個成員都設定了相應的外邊距、邊框、內邊距,為了方便表述把每個成員的外邊距寬度的左右寬度和表述為 margin,類似的表述為 border、padding,在下面的計算中會有提及。最後把每個成員的內容區域寬度表述為 content。在下面的例子中我們會發現,成員的最終寬度大小是由成員的 width flex-basic 和 content 值共同決定,在不同狀態下也會分別受到 flex-grow 和 flex-shrink 的影響 

下面的 flex 佈局中設定了 6 個成員,每個成員都設定了不同的 width flex-basic content flex-grow 值

<div class="wrapper">
  <h1>test</h1>
  <style>
    .container { display: flex; background-color: #915151;}
    .item { height: 80px; text-align: center; background-color: rgb(214, 120, 52);border-radius: 1em; border: 5px solid #000;text-align: left;margin: 15px; padding: 10px }  
</style>
  <div class="container">
    <div class="item" style="flex-grow: 1;width: 100px; flex-basis: 0">     
      <div style="width: 40px"></div>
    </div>
    <div class="item" style="flex-grow: 0; width: 250px; flex-basis: 200px">      
        <div style="width: 300px"></div>    
    </div>
    <div class="item" style="flex-grow: 2; flex-basis: 200px">      
        <div></div>    
    </div>
    <div class="item" style="flex-grow: 2;  width: 70px">      
        <div></div>    
    </div>
    <div class="item" style="flex-grow: 2;">      
        <div style="font-family: monospace;">12345678910111213141516</div>    
    </div>
    <div class="item" style="flex-grow: 3; width: 200px;">      
        <div style="width: 100px"></div>    
    </div>
  </div>
</div>複製程式碼

下面是生成佈局的截圖,包括了每個成員的尺寸

css flex佈局 精確計算成員寬度值

css flex佈局 精確計算成員寬度值css flex佈局 精確計算成員寬度值css flex佈局 精確計算成員寬度值css flex佈局 精確計算成員寬度值

css flex佈局 精確計算成員寬度值css flex佈局 精確計算成員寬度值

最內層的區域就是成員的大小區域,也就是成員的寬度和高度值,從寬度值可以看出,有的元素的寬度值和成員的 content 大小一樣, 有的和成員的 width 值一樣,有的和兩者都不同,下面將通過公式來計算各個成員的最終寬度值。

由於不是每個成員都設定了相應的 flex-basis、width 和 內容的 width 值,所以我先定下下面的規則來補充缺失的引數值,設定 t-flex-basis 為轉化的 flex-basis 值, t-width 為轉化的 width 值,t-content 為轉化的 content 值

  1. 成員的 flex-basis 值存在, 那麼 t-flex-basis 值就是該值,如果不存在,那麼 t-flex-basis 值就是 t-width 的值
  2. 成員的 width 值存在, 那麼 t-width 的值就是該值,如果不存在,,那麼 t-width 值就是 t-content 的值
  3. 成員的 content 設定了 width 值, 那麼 t-content 的值就是該值,如果不存在, 那麼 t-content 值就是內部的首選最小尺寸值,像成員 3, 4 內部沒有字元,那麼其 t-content 就是 0,而成員 5 有一串不斷行的字元,我故意把字型設定為等寬字型,在我的瀏覽器中每個字元是 6.5px,這裡有 23 個字元,所以 t-content 就為 149.5px

通過以上規則,就可以把成員的引數值轉化為下表的資料

css flex佈局 精確計算成員寬度值

下面可以開始計算最終寬度值

計算步驟

  1. 獲取剩餘空間
  2. 分配剩餘空間
  3. 找出不符合分配的成員,重新計算分配空間


獲取剩餘空間

剩餘空間 = 容器可分配空間 -  每個成員(margin + border + padding + t-flex-basis)的和

在我這裡的容器寬度是 1349px,這個也是這裡的容器可分配空間

剩餘空間 = 1349 - (30 + 10 + 20 + 0) - (30 + 10 + 20 + 200) - (30 + 10 + 20 + 200) - (30 + 10 + 20+ 70) - (30 + 10 + 20 + 149.5) - (30 + 10 + 20 + 200)  = 169.5

分配剩餘空間

分配的剩餘空間 = 剩餘空間 * (當前成員的 flex-grow) / 待分配成員的 flex-grow 值之和

item1 分配的剩餘空間  = 169.5 * 1 / (1 + 0 + 2 + 2 + 2 + 3) = 16.95

item2 分配的剩餘空間 = 169.5 * 0 / (1 + 0 + 2 + 2 + 2 + 3) = 0

item3 分配的剩餘空間 = 169.5 * 2 / (1 + 0 + 2 + 2 + 2 + 3) = 33.9

item4 分配的剩餘空間 = 169.5 * 2 / (1 + 0 + 2 + 2 + 2 + 3) = 33.9

item5 分配的剩餘空間 = 169.5 * 2 / (1 + 0 + 2 + 2 + 2 + 3) = 33.9

item6 分配的剩餘空間 = 169.5 * 3 / (1 + 0 + 2 + 2 + 2 + 3) = 50.85

找出不符合分配的成員,重新計算分配空間

所謂不符合分配的成員指的是 (分配的剩餘空間 + t-flex-basis) < min(t-content, t-width)

當成員不符合分配時,那麼該成員的最終值就是 t-content 和 t-width 中的最小值

否則成員的最終值就是:分配的剩餘空間 + t-flex-basis 

通過比較可以得到 item1 不符合分配

item1 (0 + 16.95) < min(t-content,t-width) = 40

所以 item1 的最終寬度是 40

由於 item1 如果是按分配應該是 16.95,現在是 40,多佔據了 23.05

所以要重新分配空間,分配的空間中要減少相應比例的空間

item2 重新分配後的剩餘空間 = 0 - 23.05 * 0 / (0 + 2 + 2 + 2 + 3) = 0

item3 重新分配後的剩餘空間 = 33.9 - 23.05 * 2 / (0 + 2 + 2 + 2 + 3) = 28.78

item4 重新分配後的剩餘空間 = 33.9 - 23.05 * 2 / (0 + 2 + 2 + 2 + 3) = 28.78

item5 重新分配後的剩餘空間 = 33.9 - 23.05 * 2 / (0 + 2 + 2 + 2 + 3) = 28.78

item6 重新分配後的剩餘空間 = 50.85 - 23.05 * 3 / (0 + 2 + 2 + 2 + 3) = 43.17

通過比較可以得到 item2 不符合分配

item2 (0 + 200) < min(250, 300) = 250

所以 item2 的最終寬度是 250

由於 item2 如果是按分配應該是 200,現在是 250,多佔據了 50

所以要重新分配空間,分配的空間中要減少相應比例的空間

item3 重新分配後的剩餘空間 = 28.78 - 50 * 2 / (2 + 2 + 2 + 3) = 17.67

item4 重新分配後的剩餘空間 = 28.78- 50 * 2 / (2 + 2 + 2 + 3) = 17.67

item5 重新分配後的剩餘空間 = 28.78- 50 * 2 / (2 + 2 + 2 + 3) = 17.67

item6 重新分配後的剩餘空間 = 43.17 - 50 * 3 / (2 + 2 + 2 + 3) = 26.5

通過比較可以得到以上的成員都符號分配

item3 的最終寬度是 17.67 + 200 = 217.67

item4 的最終寬度是 17.67 + 70= 87.67

item5 的最終寬度是 17.67 + 149.5= 167.17

item6 的最終寬度是 26.5 + 200 = 226.5

可以看到計算結果和最終的結果是一致的

以上是關於在伸長狀態下成員的計算規則,那麼在收縮狀態下,上面的計算公式還能起作用嗎


下面的 flex 佈局中設定了 6 個成員,每個成員都設定了不同的 width flex-basic content flex-shrink 值, 為了讓元素處於收縮狀態,我修改了成員的大小

<div class="wrapper">  
    <h1>test</h1>  
    <style>    
        .container {display: flex; background-color: #915151;}
        .item {height: 80px; text-align: center; background-color: rgb(214, 120, 52); border-radius: 1em;border: 5px solid #000;text-align: left;margin: 15px;padding: 10px}  
    </style>  
    <div class="container">    
        <div class="item" style="flex-shrink: 1;width: 100px; flex-basis: 500px">      
            <div style="width: 40px"></div>    
        </div>   
        <div class="item" style="flex-shrink: 0; width: 250px; flex-basis: 300px">      
            <div style="width: 300px"></div>    
        </div>    
        <div class="item" style="flex-shrink: 2; flex-basis: 500px">      
            <div></div>    
        </div>    
        <div class="item" style="flex-shrink: 2;  width: 400px">      
            <div></div>    
        </div>    
        <div class="item" style="flex-shrink: 2;">      
            <div style="font-family: monospace;">12345678910111213141516</div>    
        </div>    
        <div class="item" style="flex-shrink: 3; width: 300px;">      
            <div style="width: 100px"></div>    
        </div>  
      </div>  
</div>複製程式碼

下面是生成佈局的截圖,包括了每個成員的尺寸

css flex佈局 精確計算成員寬度值css flex佈局 精確計算成員寬度值css flex佈局 精確計算成員寬度值css flex佈局 精確計算成員寬度值css flex佈局 精確計算成員寬度值css flex佈局 精確計算成員寬度值css flex佈局 精確計算成員寬度值

下面將通過公式計算成員最終的寬度值

按照前面提及的轉化規則,得到類似下面的表格

css flex佈局 精確計算成員寬度值

依據上面提到的計算步驟進行計算


獲取剩餘空間

剩餘空間 = 容器可分配空間 - 每個成員(margin + border + padding + t-flex-basis)的和

在我這裡的容器寬度是 1349px,這個也是這裡的容器可分配空間

剩餘空間 = 1349 - (30 + 10 + 20 + 500) - (30 + 10 + 20 + 300) - (30 + 10 + 20 + 500) - (30 + 10 + 20+ 400) - (30 + 10 + 20 + 149.5) - (30 + 10 + 20 + 300) = -1160.5

分配剩餘空間

分配的剩餘空間 = 剩餘空間 * (當前成員的 flex-shrink) / 待分配成員的 flex-shrink值之和

item1 分配的剩餘空間 = -1160.5 * 1 / (1 + 0 + 2 + 2 + 2 + 3) = -116.05

item2 分配的剩餘空間 = -1160.5 * 0 / (1 + 0 + 2 + 2 + 2 + 3) = 0

item3 分配的剩餘空間 = -1160.5 * 2 / (1 + 0 + 2 + 2 + 2 + 3) = -232.1

item4 分配的剩餘空間 = -1160.5 * 2 / (1 + 0 + 2 + 2 + 2 + 3) = -232.1

item5 分配的剩餘空間 = -1160.5 * 2 / (1 + 0 + 2 + 2 + 2 + 3) = -232.1

item6 分配的剩餘空間 = -1160.5 * 3 / (1 + 0 + 2 + 2 + 2 + 3) = -348.15

找出不符合分配的成員,重新計算分配空間

所謂不符合分配的成員指的是 (分配的剩餘空間 + t-flex-basis) < min(t-content, t-width)

當成員不符合分配時,那麼該成員的最終值就是 t-content 和 t-width 中的最小值

否則成員的最終值就是:分配的剩餘空間 + t-flex-basis

通過比較可以得到 item5 不符合分配

item5 (-232.1 + 149.5) < min(t-content,t-width) = 149.5

所以 item5 的最終寬度是 149.5

由於 item5 如果是按分配應該是 -82.6,現在是 149.5,多佔據了 232.1

所以要重新分配空間,分配的空間中要減少相應比例的空間

item1 重新分配後的剩餘空間 = -116.05 - 232.1 * 1 / (1 + 0 + 2 + 2 + 3) = -145.0625

item2 重新分配後的剩餘空間 = 0 - 232.1 * 0 / (1 + 0 + 2 + 2 + 3) = 0

item3 重新分配後的剩餘空間 = -232.1 - 232.1 * 2 / (1 + 0 + 2 + 2 + 3) = -290.125

item4 重新分配後的剩餘空間 = -232.1 - 232.1 * 2 / (1 + 0 + 2 + 2 + 3) = -290.125

item6 重新分配後的剩餘空間 = -348.15 - 232.1 * 3 / (1 + 0 + 2 + 2 + 3) = -435.1875

通過比較可以得到 item6 不符合分配

item6 (-435.1875 + 300) < min(300, 100) = 100

所以 item6 的最終寬度是 100

由於 item6 如果是按分配應該是 -135.1875,現在是 100,多佔據了 235.1875

所以要重新分配空間,分配的空間中要減少相應比例的空間

item1 重新分配後的剩餘空間 = -145.0625 - 235.1875 * 1 / (1 + 0 + 2 + 2) = -192.1

item2 重新分配後的剩餘空間 = 0 - 235.1875 * 0 / (1 + 0 + 2 + 2) = 0

item3 重新分配後的剩餘空間 = -290.125- 235.1875 * 2 / (1 + 0 + 2 + 2) = -384.2

item4 重新分配後的剩餘空間 = -435.1875 - 235.1875 * 2 / (1 + 0 + 2 + 2) = -529.2625

通過比較可以得到 item4 不符合分配

item4 (-529.2625 + 400) < min(t-content,t-width) = 0

所以 item4 的最終寬度是 0

但是可以看到 item4 實際的渲染結果不是 0,所以以上的計算公式在收縮狀態下是無效的,問題出現在計算剩餘空間的計算上,在收縮狀態下的計算公式是

分配的剩餘空間 = 剩餘空間 * (當前成員的 flex-shrink * t-flex-basis) / 待分配成員的(flex-shrink * t-flex-basis)之和

css flex佈局 精確計算成員寬度值

重新計算以上的步驟

分配剩餘空間

分配的剩餘空間 = 剩餘空間 * (當前成員的 flex-shrink * t-flex-basis) / 待分配成員的(flex-shrink * t-flex-basis)之和

item1 分配的剩餘空間 = -1160.5 * 1 * 500 / (1 * 500 + 0 * 300 + 2 * 500 + 2 * 400 + 2 * 149.5 + 3 * 300) = -165.83

item2 分配的剩餘空間 = -1160.5 * 0 * 300 / (1 * 500 + 0 * 300 + 2 * 500 + 2 * 400 + 2 * 149.5 + 3 * 300) = 0

item3 分配的剩餘空間 = -1160.5 * 2 * 500 / (1 * 500 + 0 * 300 + 2 * 500 + 2 * 400 + 2 * 149.5 + 3 * 300) = -331.666

item4 分配的剩餘空間 = -1160.5 * 2 * 400 / (1 * 500 + 0 * 300 + 2 * 500 + 2 * 400 + 2 * 149.5 + 3 * 300) = -265.33

item5 分配的剩餘空間 = -1160.5 * 2 * 149.5 / (1 * 500 + 0 * 300 + 2 * 500 + 2 * 400 + 2 * 149.5 + 3 * 300) = -99.168

item6 分配的剩餘空間 = -1160.5 * 3 * 300 / (1 * 500 + 0 * 300 + 2 * 500 + 2 * 400 + 2 * 149.5 + 3 * 300) = -298.5

找出不符合分配的成員,重新計算分配空間

所謂不符合分配的成員指的是 (分配的剩餘空間 + t-flex-basis) < min(t-content, t-width)

當成員不符合分配時,那麼該成員的最終值就是 t-content 和 t-width 中的最小值

否則成員的最終值就是:分配的剩餘空間 + t-flex-basis

通過比較可以得到 item5 不符合分配

item5 (-99.168 + 149.5) < min(t-content,t-width) = 149.5

所以 item5 的最終寬度是 149.5

由於 item5 如果是按分配應該是 50.33,現在是 149.5,多佔據了 99.17

所以要重新分配空間,分配的空間中要減少相應比例的空間

item1 分配的剩餘空間 = -165.83 - 99.17 * 1 * 500 / (1 * 500 + 0 * 300 + 2 * 500 + 2 * 400 + 3 * 300) = -181.325

item2 分配的剩餘空間 = 0 - 99.17 * 0 * 300 / (1 * 500 + 0 * 300 + 2 * 500 + 2 * 400 + 3 * 300) = 0

item3 分配的剩餘空間 = -331.666 - 99.17* 2 * 500 / (1 * 500 + 0 * 300 + 2 * 500 + 2 * 400  + 3 * 300) = -362.65

item4 分配的剩餘空間 = -265.33 - 99.17 * 2 * 400 / (1 * 500 + 0 * 300 + 2 * 500 + 2 * 400  + 3 * 300) = -290.1225

item6 分配的剩餘空間 = -298.5 - 99.17 * 3 * 300 / (1 * 500 + 0 * 300 + 2 * 500 + 2 * 400 + 3 * 300) = -326.39

通過比較可以得到 item6 不符合分配

item6 (-326.39 + 300) < min(300, 100) = 100

所以 item6 的最終寬度是 100

由於 item6 如果是按分配應該是 -26.39,現在是 100,多佔據了 126.39

所以要重新分配空間,分配的空間中要減少相應比例的空間

item1 分配的剩餘空間 = -181.325 - 126.39 * 1 * 500 / (1 * 500 + 0 * 300 + 2 * 500 + 2 * 400) = -208.8

item2 分配的剩餘空間 = 0 - 126.39 * 0 * 300 / (1 * 500 + 0 * 300 + 2 * 500 + 2 * 400) = 0

item3 分配的剩餘空間 = -362.65 - 126.39* 2 * 500 / (1 * 500 + 0 * 300 + 2 * 500 + 2 * 400) = -417.6

item4 分配的剩餘空間 = -290.1225 - 126.39 * 2 * 400 / (1 * 500 + 0 * 300 + 2 * 500 + 2 * 400) = -334.08

通過比較可以得到以上成員都符號要求

item1 的最終寬度是 500 - 208.8 = 291.2

item2 的最終寬度是 0 + 300= 300

item3 的最終寬度是 -417.6 + 500= 82.4

item4 的最終寬度是 -334.08 + 400 = 65.9

可以看到計算結果和最終的結果是一致的

總結

從以上的分析和計算中,可以看到成員的寬度受多個引數所控制,主要屬性是 flex-basis, 其中在伸長和收縮狀態中,計算分配的剩餘空間的公式是不同的,而且在計算中還會受到成員內容大小和成員自身大小的影響,成員的最終值不得小於成員內容大小和成員自身大小中的最小值,否則就取這個最小值並重新分配剩餘空間。這裡可以舉一個例子,很多時候我們設定 flex: 1 並希望成員都是等分的,一般情況下是沒有問題的。其中 flex: 1 實際上是 flex-basis: 0%, flex-grow: 1, flex-shrink: 1,如果你不能控制成員的內容大小,當這個內容尺寸大於等分尺寸時,由於沒有設定 width 值, width 值預設等於內容大小,按照以上的分析,該尺寸就會改變成員寬度,造成等分的效果失效,要避免這種情況,可以設定 width 的值為 0,這樣成員內容大小和成員自身大小中的最小值就為0,等分尺寸肯定大於該值,成員尺寸便取等分尺寸,當然這個時候成員內容就會溢位。


通過以上的分析,我們對計算成員寬度已經有初步的瞭解,基本上能應對大部分的情況,但是 flex 佈局還不止以上的屬性,而且不同的屬性之間還能互相組合,甚至在有的時候可能在容器上設定了絕對定位(flex功能不會消失),或者成員上設定了 box-sizing(設定的 width 值可能不同於讀取的 width 值),或者 width 值是百分比(父元素寬度不確定時,width 的表現像 auto,等父元素確定後才開始計算),flex-grow 或者 flex-shrink 是小數(滿足特定情況計算規則會改變)等,在以上這些情況下計算時要考慮因素會更多。

為了研究 flex 佈局規則,我特意寫了一個測試頁面,大家有興趣也可以去看一下

flex 佈局測試 changk99.github.io/flexbox/

css flex佈局 精確計算成員寬度值







相關文章