September 6, 2021

[CSS] Flex/Grid Layout Modules, part 1

[CSS] Flex/Grid Layout Modules, part 1
CSS Flexible Box Layout Module

其實我不知道 Flexbox 還有誰想看?

這年頭好像用框架比較快,誰理你背後的原理是什麼呢?


Flexbox

我們先來看看可使用狀況。基本上除了 IE 以外,是不用擔心使用上的問題。

CSS Flexible Box Layout Module

Flexbox 的基本盤是對應從 CSS2.1 以來的四種編排模式,

  1. 區塊(block
  2. 行內(inline, inline block
  3. 表格資料(table, 或各種資料集設計)
  4. 定位區塊(使用 position 的各種變化)

雖然是這樣講,不過這就跟當年 CSS1/2/2.1 開始流行後,從 <table> 慢慢轉向 <div> 後,接著到了 HTML5 開始講語意化。出了一些很奇妙的事情,

  1. <table> 退流行了所以我都用 <div> 很潮~
  2. <div> 就用 position 神馬都可以排,超 DUE 的~
  3. float 簡直凌波微步,我得意的飄~
  4. HTML5 說要語意化,用 <div> 簡直罪惡!

其他的就不提了,我只想說,為了語意化而語意化真的挺噁心的。


基本介紹

Flexbox 區塊元件主要構成如下,

  1. Flex 容器(Container
  2. Flex 元件(Items
  3. 容器尺寸(通常我們叫做 寬度
  4. 交叉軸尺寸(通常我們叫做 高度
  5. 容器主軸/交叉軸(依據排列方式決定哪一個方向為主軸)
Flex 容器以 row 為方向設定時

如果你把 Flex 容器主軸方向設定為 column 的時候,上圖的主要軸、交叉軸就會交換。


Flex 容器

設定容器我們可以使用 display 來達成,容器可以設定成兩種,

  1. display: flex 亦即使用區塊(Box Level)來設定容器。
  2. display: inline-flex 亦即使用行內(Inline Level)來設定容器。

無輪你設定的是哪一種,需要留意 Flex 容器會忽略以下特性,

  1. float, clear 會被忽略。
  2. vertical-align 無法應用在 Flex 元件上。
  3. ::first-line, ::first-letter 這兩個擬似元件無法套用至容器上。

倘若元素指定 display: inline-flex 的話,在計算樣式值(Computed Value)會呈現 flex 而不是 inline-flex,這一點請留意。


樣式設定

在容器當中,以下列舉常用的樣式設定,

樣式 預設值
flex-direction row, row-reverse, column, column-reverse row
flex-wrap nowrap, wrap, wrap-reverse nowrap
flex-flow <flex-direction> <flex-wrap> 無預設值

容器主要軸、交叉軸會因為方向性的不同而交換,其下屬性所適用的方向也會不同。

另,軸方向也會因為 writing-mode 的影響而有不一樣的呈現,關於 Write Mode 可以參考 w3c 上面的說明(CSS Writing Modes Level 4)。

容器中對於影響元件的屬性有這些,

樣式 預設值
justify-content flex-start, flex-end, center, space-between, space-around flex-start
align-items flex-start, flex-end, center, baseline, stretch stretch
align-content flex-start, flex-end, center, space-between, space-around, stretch stretch

需要留意的是 align-content 只能適用在多行的 Flex 容器當中,在預設的容器設定中使屬於單行 Flex 容器,套用這個樣式是無效的設定。


軸方向

容器些樣式設定會因為軸方向性的不同而應用在不同的方向上(會跟著軸轉動)。請留意一個點,軸方向並不總是內容流向,我所舉的例子都是以慣用方向為主(由左至右,由上到下)。

舉個例子來說,

兩種方向設定的容器

另外關於 align-content 則是在多行容器中才會套用到這個樣式。所謂的 多行 的意思即是 wrap 的情況下,產生超過一行的 Flex 元件時,該樣式就會被套用。

請留意 column 這個方向在沒有容器尺寸的情況下,無法產生 wrap 的效果,亦即並無 多行 的情況存在,且關於 wrap 的相關樣式也會失效。

舉一個套用 wrap 產生多行 Flex 容器的例子,

當容器產生多行時,才會套用 align-content 樣式。

跟其他兩個樣式相同,當軸方向轉動時,align-content 也會跟著軸轉動,亦即應用的方向會不同。


Flex 元件

任何被 Flex 容器所包含的第一層子元件,都會被轉成 Flex 元件,而自身屬性會轉換成 Flex 格式內容(Flex formatting context),可以當作一般區塊元件看待,但實際上並不是(Like Box Model, bot NOT.)。如同上述所說,這些元件並無法使用 float 爾等設定,這一點需特別留意。另外,假設這些元件被定義了靜態定位(positionstatic, absolute)時,該元件就不屬於 Flex 容器預設的主軸流向,會跳脫任何關於 Flex 容器所帶來的影響。

元件自身也可以使用 display: flex 來將自己轉變成容器,這樣的作法在需要複雜結構時,可以自由變化使用。


樣式設定

Flex 元件有以下樣式可以使用,

樣式 預設值
flex-grow 數字(負值無效) 0
flex-shrink 數字(負值無效) 0
flex-basis auto, content寬度 auto
flex none<flex-grow> <flex-shrink> <flex-basis> 0 1 auto
order 數字(可為負值) 0
align-self auto, flex-start, flex-end, center, baseline, stretch auto

其中 flex 除了 none 外,還有幾個關鍵字可以使用,

關鍵字 等值
none 0 0 auto
initial 0 1 auto
auto 1 1 auto
<正整數> <正整數> 1 0

flex, flex-grow, flex-shrink, flex-basis

在絕大多數的 CSS 框架中,我們比較常見的設定大多都是這些組合,

flex: 0 1 auto;
// 或
flex: 1 0 100%;
// 或
flex: 1 0 50%;
max-width: 50%;

然後就沒有然後了。因為人家這樣用所以你就跟著這樣用,至於為什麼要這樣用好像也不是挺重要的事情。

沒關係,我們就來看看到底發生了什麼事情?

屬性 說明
flex-grow 將元件依照此設定數字的權重來填滿容器的剩餘空間。
flex-shrink 當容器剩餘空間不足時,依照此權重來壓縮元件。
flex-basis 在水平(row)排列容器中,等同於 width 樣式,在同時設定時將會忽略 width 的樣式設定。但若為 autocontent 時,則 width 樣式將會覆蓋。而當你設定為 0 時,結果會跟 content 雷同,但權重不同。

flex-grow 計算方式,可以依照此公式來計算,

剩餘空間 x <flex-grow> / sum(<flex-grow>)

剩餘空間怎麼來的呢?

容器空間 - sum(<flex-basis>||<width>)

但是,

當你的 flex-grow 總和小於 1 的時候,事情就不是這樣了。在所有 Flex 元件的 flex-grow 總和小於 1 時,該總和會直接當作 1 來使用。換句話說,上面的公式會變成,

剩餘空間 x <flex-grow> / 1

這樣計算下來,容器就可能會有剩餘空間沒有分配的情況。

另外,當你的 flex-grow 有搭配 max-width 使用時,所計算出來的元件若大於 max-width,則會優先取用 max-width 的設定值來使用,計算出來的填滿尺寸將會被忽略。這也是造成容器沒有被填滿的另外一個原因。

flex-grow 示意圖

flex-shrink 計算方式,可以依照此公式來計算,

溢出空間 x <flex-shrink> * <width>) / sum(<flex-shrink> * <width>)

溢出空間的計算方式為(注意,這為負值),

容器空間 - sum(<flex-basis>||<width>)

flex-grow 雷同,當你的 flex-shrink 總和小於 1 的時候,他並不會拿所有的溢出空間來計算,你的真實的溢出空間要先經過這樣的計算,

溢出空間 x sum(<flex-shrink>) / 1

算出新的溢出空間之後,才回頭套用上面的公式去計算。

flex-shrink 示意圖

flex-basis 的魔術

特別把這件事情拿出來講的原因,是因為他跟 width 實在有說不清的愛恨糾葛。如同上述提及了一些優先權的事情,這邊給一個比較清楚的比較表,

設定 寬度有效值
flex-basis: 50px; 50px
flex-basis: 50px; width: auto; 50px
flex-basis: content; width: 60px; 60px
flex-basis: 70px; width: 60px; 70px
flex-basis: auto; width: 80px; 80px
flex-basis: 90%; width: 80px; 90%
flex-basis: 90%; max-width: 80%; 80%

基本上,只要 flex-basis 不是使用關鍵字 auto, content 的情況下,優先權一律覆蓋 width 的設定。但是,這個數值設定會受到 max-width 的影響而有所不同。

受限於 max-width 產生的未填滿空間

align-self

這個 Flex 元件屬性有一個 必要條件,倘若以 row 為主軸方向,你的容器必須要有交叉軸的尺寸設定,不然這個屬性是無法有效呈現的。

align-self 必須在容器有指定交叉軸尺寸下才會生效

order

故名思義就是 Flex 元件順序的設定,但請留意,如果你的元件使用了靜態定位設定(positionstatic, absolute)時,因為元件會跳脫 Flex 主軸流向,所以此時的順序設定會失效。

Flex 元件會有一個隱性的定義,在 order 樣式混用的情況下,沒有設定 order 樣式的 Flex 元件,會帶有一個 order: 0 的效果。所以,若你在多個元件使用 order 時,請務必確認每個元件都有設定你想要的 order 樣式。

舉例來說,

<div class="flex">
    <div class="item item-1"></div>
    <div class="item item-2"></div>
    <div class="item item-3"></div>
</div>
.flex {
    display: flex;
    flex-flow: row;
}

.item {
    flex: 1 0 33.333333%;
    max-width: 33.333333%;
}

.item-1 {
    order: 1;
}

.item-2 {
    order: 3;
}

.item-3 {
    order: 2;
}

最終呈現的結果如圖,

有一點請留意,如果你的軸方向是反向(row-reversecolumn-reverse)的話,順序的設定也會反過來。


小記

以上這些是 Flexbox 的基本概念介紹,其實講起來並沒有很多艱深的東西,真正比較枯燥乏味的會再後面繼續介紹,如果不想理解 Flexbox 的演算機制的可以跳過沒關係(笑)。

本篇內容理論上可以應付五成以上的容器設定。為什麼只有五成?因為我還有 留白 的部分沒有講到,那些算一算大概是剩下的四成五左右。

下一篇會著墨於那個四成五,至於剩下的 5% 就放最後吧,反正都是一些很硬的演算機制。


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


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