[CSS] Flex/Grid Layout Modules, part 5

Flex 有存在剩餘空間時才能以 flex-grow 分配

數學不會背叛你,數學不會就是不會。

我現在寫三角函數都是去 Google 的,不要問。注意!本篇可能會出現大量的加減乘除,如有出現頭暈目眩、噁心想吐、手腳冰冷無力等狀況,請立即關閉本篇文章,閃光洽關心您的身體健康。

或者你要去驗孕也是可以的。


容器計算

前幾篇有提到剩餘空間、元件填充等等,我們現在就來看一下實際上 Flex 容器是怎麼做運算的。首先是 flex-grow 的計算方式,在數值總和大於 1 的一般情況下來看,但且記得,必須容器上有存在 剩餘空間 的情況下才能被分配。

Flex 有存在剩餘空間時才能以 flex-grow 分配

所以我們來描述一下我們的容器跟元件,

  1. Flex 容器為 600px 主要軸尺寸。
  2. Flex 元件 1 為寬度 200pxflex-grow 設定為 1
  3. Flex 元件 2 為寬度 100pxflex-grow 設定為 2

首先,我們可以得知剩餘空間為,

剩餘空間 = 600px - (200px + 100px) = 300px

然後我們就能依照 flex-grow 來分配,

Flex 元件 1 擴充寬度 = 300px x 1 / (1 + 2) = 100px
Flex 元件 2 擴充寬度 = 300px x 2 / (1 + 2) = 200px

所以畫面最後會得到,

Flex 元件 1 寬度 = 200px + 100px = 300px
Flex 元件 2 寬度 = 100px + 200px = 300px

看起來是不是沒有很難,只要會加減乘除就好了。然後,我在一開始有提到 flex-grow 總和小於 1 的情況,

  1. Flex 容器為 600px 主要軸尺寸。
  2. Flex 元件 1 為寬度 200pxflex-grow 設定為 0.1
  3. Flex 元件 2 為寬度 100pxflex-grow 設定為 0.2

剩餘空間就不贅述了,但是分配的方式不太一樣。

Flex 元件 1 擴充寬度 = 300px x .1 / 1 = 30px
Flex 元件 2 擴充寬度 = 300px x .2 / 1 = 60px

所以你會發現最終我們得到的總寬度是,

Flex 元件 1 寬度 = 200px + 30px = 230px
Flex 元件 2 寬度 = 100px + 60px = 160px

這也是為何容器雖然有 flex-grow 但是沒有被填滿的情況。至於 flex-shrink 的部分基本上也是雷同的,只是從擴充變成壓縮方向相反了而已。


尺寸干擾

對於 flex-grow, flex-shrink 這兩件事情,被干擾的機率其實頗高,

  1. min-width, max-width
  2. min-content, max-content
  3. max(), min()

如果不確定自己在做什麼,盡量不要在使用 flex-growflex-shrink 的情況下,使用這些設定、函數或關鍵字,還要預期會得到相對應的尺寸。

基本上在彈性設計結構的狀況下,Flex 元件尺寸應該是提供一種 確保 空間使用的狀況符合預期,請盡量不要把 Flex 元件尺寸當作是期待值。如果你要這麼做,請確保你使用的是 flex: 0 0 auto,並且保證你的元件都有相對應尺寸。

至於 minmax(),在 w3c 有特別說明 minmax() 這個 CSS Function 是給 Grid Layout 使用,所以你在 Flexbox 元件上並無法使用這個函示來計算尺寸。

回到一開始我們提到的 flex-basis 的相關描述,我們再來補充一點看起來比較 沒用 少用的寫法,這裡的重點,

請留意 width 何處會失效

設定 寬度有效值
flex-basis: max(10vw, 50rem); 計算數值,取當下最大值
flex-basis: max(10vw, 50rem); width: 60px 計算數值,取當下最大值
flex-basis: min(10vw, 50rem); width: 60px 計算數值,取當下最小值
flex-basis: min(10vw, 50rem); width: content 計算數值,取當下最小值
flex-basis: auto; width: min(10vw, 50rem); 計算數值,取當下最小值
flex-basis: auto; width: content; 計算數值,取容器內容尺寸
flex-basis: min(10vw, 50rem); max-width: 80px; 80px
flex-basis: min(10vw, 50rem); min-width: 100rem; 100rem

總結來說,除了 max-width, min-width 會強迫覆寫 flex-basis 以外,扣除 auto 會被 width 覆寫,其餘的設定都還是以 flex-basis 為準。換句話說,如果你使用以下的寫法,就會遇到一些很神奇的事情,

.flex-container {
    width: 600px;
}

.flex-item {
    flex: 0 0 content;
    width: 200px;
}

如果我們所使用的 HTML 結構像是這樣,

<div class="flex-container">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
    <div class="flex-item">3</div>
    <div class="flex-item">4</div>
</div>

根據簡單的數學,我們有 4 個容器元件,每個容器元件定義了 width: 200px,所以我們理論上會得到,4 x 200px = 800px 總共是 800px 的元件尺寸。然而,當我們的元件容器使用 flex: 0 0 content 時,我們的想像是,

  1. 不會填充
  2. 不會壓縮
  3. 依照元件內容設定尺寸

還記得我們 第一篇 寫了這個結果嗎?

設定 寬度有效值
flex-basis: content; width: 60px; 60px

所以真的嗎?我們來看看實際結果,

flex: 0 0 content; 的怪異現象

驚不驚喜?意不意外?

然而,當我們把他分開來寫的時候,

.flex-item {
    flex-grow: 0;
    flex-shrink: 0;
    flex-basis: content;
    width: 200px;
}

在這個時候他會恢復正常,這才是我們覺得不被壓縮的樣子。

為什麼?

官方有這樣的說明,

A unitless zero that is not already preceded by two flex factors must be interpreted as a flex factor. To avoid misinterpretation or invalid declarations, authors must specify a zero <‘flex-basis’> component with a unit or precede it by two flex factors.

因為 flex: 0 0 content 這樣的設定,是兩個 0 的無單位因子,再加上 content 這種無單位 flex-basis 設定,所以會造成混淆。他會被當作單一彈性因子來看待,也就是等同於只設定了 flex-growflex-basis 兩組設定而已。

為何只有這兩組?請看原始 flex 的設定值,

none | [ <‘flex-grow’> <‘flex-shrink’>? || <‘flex-basis’> ]

這樣可以理解為何會出錯了吧。由於 flex-shrink 預設為 1,所以當我們照剛剛的寫法來做的時候,就會被轉成 flex: 0 1 content 這樣的結果,並不會是你所設定的 flex: 0 0 content後者的設定是不太合法 的寫法。

不能說寫錯,而是在理解上官方的規定如此。

auto 不在此限。


零尺寸元件

另外一點,雖然你可以定義 Flex 元件尺寸,但不代表這個元件尺寸不會發生零尺寸(zero-sized)的情況。根據 Flex 容器的特性,這些所謂的零尺寸元件,會盡可能的被放在同一行,也就是說,在多行的情況下,即便第一行最後一個元件剛好填滿容器,在下一行開始之前的零尺寸元件,都會被放在上一行裡面。

其實零尺寸元件除了是空白元件外,也可能是寬度設定為 0 的元件。


關於 gap

CSS Box Alignment Module Level 3 當中,已經提供了 gap 的樣式可以使用,目前的支援度來說也算不錯,

gap 在 Flexbox 中的使用支援度

前幾篇提到了 gap 這件事情,有這個樣式就能解決使用 paddingmargin 的寬度問題。

.flex-container {
    display: flex;
    gap: 10px;
}

請留意,gap 還是有分軸方向,所以會有以下幾種寫法,

  1. column-gap 交叉軸方向的間隔。
  2. row-gap 主要軸方向的間隔。
  3. gap 兩個軸方向的間隔,等於上述兩個縮寫。
Flex 目前可以使用 gap 樣式來設定間隔

但是,這不是沒有後遺症的。

<div class="flex-container">
  <div class="flex-item"></div>
  <div class="flex-item"></div>
  <div class="flex-item"></div>
  <div class="flex-item"></div>
</div>

我們在這種結構下使用 gap 的設定,

.flex-container {
    display: flex;
    width: 800px;
    gap: 10px;
}

.flex-item {
    flex: 0 0 auto;
    width: 200px;
}

最終會造成什麼結果呢?

主要容器會被擴充到填滿元件為止

請注意,如果你的元件 flex-shrink 設定為 0。那麼,gap 的空間就會將 Flex 容器撐開,也就是,當你的 Flex 容器使用 overflow: hidden 的話,最後一個 Flex 元件會被切斷(因為超出了原本寬度設定了)。

而,若你將 flex-shrink 設定為非零值,那麼,你的 Flex 容器尺寸會生效,gap 的尺寸加上 Flex 元件尺寸,會視為 溢出尺寸 來處理,只是,gap 尺寸是 永遠不會被壓縮,所以倒楣的就是 Flex 元件。


小記

Flexbox 其實也沒有很複雜,最麻煩的其實還是尺寸處理。剩下的就交給上天安排就可以了,如果發現不對勁可以擲茭問一下媽祖也是可以的。

Flexbox 告一段落,明天會開始講 Grid 的部分。其實無論是 Flex 還是 Grid,多半都會牽扯到很多其他的模組,不過全部拉進來講會過於離題,所以 Flex 的部分就到此為止。

Flexbox 小遊戲,有興趣的可以玩玩看。
https://flexboxfroggy.com

Chrome Flexbox bug list
https://bugs.chromium.org/p/chromium/issues/list?q=component:Blink>Layout>Flexbox

Firefox Flexbox bug list
https://bugzilla.mozilla.org/buglist.cgi?quicksearch=flexbox

Safari Flexbox bug list
https://bugs.webkit.org/buglist.cgi?quicksearch=flexbox


目錄與小節:
[CSS] Flex/Grid Layout Modules, part 0


鐵人賽同步放送:
https://ithelp.ithome.com.tw/articles/10261021

Hina Chen
偏執與強迫症的患者,算不上是無可救藥,只是我已經遇上我的良醫了。
Taipei