iT邦幫忙

2021 iThome 鐵人賽

DAY 1
0
Modern Web

CSS Flex/Grid Layout Modules系列 第 1

[CSS] Flex/Grid Layout Modules, part 1

萬事起頭難,只要不起頭,就一點都不難了。

在這個充滿著 CSS Frameworks 的年代,人人有功練,人人有版切,身為老屁股的我只能在沙灘上曬乾,而且還賣不到錢。

鐵人賽就當作自己還有在喘氣的證明。


前言

其實寫 CSS 框架應該比較多觀眾,但就一個碼農來說,自己動手做會比較有趣一點。而且那些框架實在有點五花八門,就是你去 Google 一些關鍵字,諸如「超好用」「XX 天就上手」「20XX 熱門框架」然後就會跑出一堆。

我沒有阻止你用,只要能賺錢的框架就是好框架。

君不見 jQuery 的 $ 還是屹立不搖。

不過 jQuery 有點離題了就是。


目前 w3c 對於 Flexbox, Grid 這兩件事情算是有認真在更新。雖然說 Grid 已經 CR 不過又掛上 Draft,該吵的東西也還沒有結論(例如 subgrid 會推遲到 Level 2 才可能會推出 #Issue958)。

Grid 從 2016 年 CR 至今也 5 個年頭了,基本上在 Caniuse 上面也呈現出不錯的支援度。熱門的 TailwindCSS 基本上也有採用 Grid 來做排版,至於怎麼用不要來問我,我沒有很熟(哈)。

Flexbox 就不用再多說,我在 2012 年看他 CR 後,又到了 2016 才慢慢被推廣出來,算起來已經 9 年了。至於說為什麼那麼不紅(或是不普及?),可能要去問一下前陣子才入土的 IE 吧。

至於所搭配的 Media Query 會在系列中提及,會順便講一點,不會著墨太多。


目錄

  1. Flexbox 基本介紹
  2. Flexbox 能與不能
  3. Flexbox 與 Media Query
  4. Flexbox 其他相關資訊
  5. Grid 基本介紹
  6. Grid 能與不能
  7. Grid 與 Media Query
  8. Grid 演算機制
  9. Grid 與 Flexbox 比較
  10. Grid / Flexbox 混合應用
  11. Grid 進階與新功能
  12. Grid 在瀏覽器上的各種差異
  13. 那些跟 Grid 相關的 CSS 樣式設定
  14. 那些關於 Level 2 的事情

以上這些,其實就把超長篇幅的文章然後分割成 30 份這樣吧(哈)。


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

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


Flexbox

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

https://ithelp.ithome.com.tw/upload/images/20210906/20001433IXK91r6iZh.png

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. 容器主軸/交叉軸(依據排列方式決定哪一個方向為主軸)

https://ithelp.ithome.com.tw/upload/images/20210906/200014335PMJSlRi4i.png

如果你把 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 容器,套用這個樣式是無效的設定。


軸方向

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

舉個例子來說,

https://ithelp.ithome.com.tw/upload/images/20210906/20001433rwo2U4VUpP.png

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

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

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

https://ithelp.ithome.com.tw/upload/images/20210906/2000143320p6fegr8X.png

跟其他兩個樣式相同,當軸方向轉動時,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 的設定值來使用,計算出來的填滿尺寸將會被忽略。這也是造成容器沒有被填滿的另外一個原因。

https://ithelp.ithome.com.tw/upload/images/20210906/20001433sr8P3Le18a.png

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

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

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

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

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

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

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

https://ithelp.ithome.com.tw/upload/images/20210906/20001433bn66UeWPUg.png


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 的影響而有所不同。

https://ithelp.ithome.com.tw/upload/images/20210906/20001433C0rk1wy4QA.png


align-self

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

https://ithelp.ithome.com.tw/upload/images/20210906/20001433Y5i13Xb7b4.png


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;
}

最終呈現的結果如圖,

https://ithelp.ithome.com.tw/upload/images/20210906/20001433CK1dOwfz89.png

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


小記

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

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

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


同步放送:

[CSS] Flex/Grid Layout Modules, part 0
[CSS] Flex/Grid Layout Modules, part 1


下一篇
[CSS] Flex/Grid Layout Modules, part 2
系列文
CSS Flex/Grid Layout Modules14

尚未有邦友留言

立即登入留言