CSS

CSS Grid 深入淺出

CSS Flexbox 深入淺出

上一篇的 Flexbox 徹底改變了我們在網頁上排版的方式,但它其實只是整個佈局系統 (Layout) 的一部分。它還有個大哥就是所謂的網格佈局模組 (Grid Layout) 。這兩個佈局系統加起來,提供了一套非常完整又強大的佈局工具。

CSS 網格允許你定義出列與行,做出「二維佈局」,然後再把項目放進這個網格裡。有些項目可能只佔一個格子,或是跨好幾列或好幾行,我們可以很精確地設定每個格子的大小,也可以讓它們根據內容自動調整。項目可以精確地放在某個格子裡,也可以讓它們自己流動去填補空位。透過網格,我們可以打造出非常複雜的版面設計,就像下圖。

CSS Grid Layout

基礎:Basic Grid

我們先從一個很基礎的範例開始:把六個框排成三列。如下圖:

CSS Grid Layout

1
2
3
4
5
6
7
8
<div class="grid">    
<div class="a">a</div>
<div class="b">b</div>
<div class="c">c</div>
<div class="d">d</div>
<div class="e">e</div>
<div class="f">f</div>
</div>

就像 Flexbox 一樣,Grid 也是作用在 DOM 的父子層級。當你把某個元素設為 display: grid,它就變成了「網格容器」 (Grid Container) ,而它底下的子元素就會變成「網格項目」 (Grid Items)

1
2
3
4
5
6
7
8
9
10
11
12
.grid {
display: grid; /* 使元素變成網格容器 */
grid-template-columns: 1fr 1fr 1fr; /* 定義三個等寬的列 */
grid-template-rows: 1fr 1fr; /* 定義兩個等高的行 */
gap: 0.5em; /* 設定間距 */
}
.grid > * {
background-color: darkgray;
color: white;
padding: 2em;
border-radius: 0.5em;
}

我們用了 display: grid 把元素變成了「網格容器」。這個容器的行為就像一般的 block 區塊元素一樣,會自動填滿整個可用寬度。

雖然這裡沒寫到,不過你也可以用 inline-grid,這會讓元素像 inline 行內元素那樣跟文字一起流動,而且寬度只會剛好包住它的子元素。不過實際上,大多數情況還是會用 display: grid,用 inline-grid 的機會比較少。

接下來是兩個新的屬性:grid-template-columnsgrid-template-rows,用來定義每一列和每一行的大小。

這裡我們用了一個新的單位 fr,它的意思是「比例單位」,有點像 Flexbox 裡的 flex-grow。例如,grid-template-columns: 1fr 1fr 1fr 就是建立三個等寬的欄位,每個欄都佔一等份。不一定每一列或每一行都得用 fr,也可以用其他單位像是 pxem%,甚至可以混著用。像這樣:grid-template-columns: 300px 1fr,意思是第一欄寬度固定 300px,第二欄則佔據剩下所有可用空間。如果你寫 2fr,那它的寬度就是 1fr 的兩倍。

還有一個很實用的屬性是 gap,它是用來設定網格之間的間距,就跟 Flexbox 裡的 gap 一樣用法。你也可以一次設定兩個值,像是 gap: 0.5em 1em,這樣就能分別指定垂直和水平的間距。

在剛開始定案網格規範的時候,gap 這個屬性原本叫做 grid-gap,所以可能會在一些早期的範例中看到這個名稱。其實它的功能跟現在的 gap 是一樣的。後來 CSS 規範更新了,改為比較通用的 gap,而且連 Flexbox 也一起加入支援,變得更一致好用。

剖析:Grid

想要真正掌握網格系統,就要先搞懂它的各個術語。接下來,在網格系統裡,還有四個術語是一定要知道的:

  • 網格線 (Grid Lines) :網格線構成整個網格的結構,可以是垂直的或水平的,分別出現在列或行的兩邊。如果你有設 gap 的話,網格線就會在間距的兩側,或是說間距會出現在網格線的之間。
  • 網格軌道 (Grid Tracks) :是指兩條相鄰網格線之間的區域。軌道可以是橫向的 (行) 或直向的 (列) 。
  • 網格單元格 (Grid Cells) :網格裡的每一格,簡單來說,就是橫向和直向的軌道交錯出來的空間。
  • 網格區域 (Grid Areas) :由一個或多個單元格組成的長方形區塊,範圍是由兩條垂直網格線和兩條水平網格線包起來的。

CSS Grid Layout

建立網格佈局的過程中,會不斷參考網格裡的這些基本結構。舉個例子來說,當你寫下 grid-template-columns: 1fr 1fr 1fr,其實就是定義了三個等寬的垂直「網格軌道」。這樣一來,也就產生了四條垂直的「網格線」:一條在網格線的最左邊、兩條在每個欄位之間,還有一條在最右邊。

Note:你的設計不一定要把網格的每個格子都填滿!如果你有區域想要留空白,只要讓那個格子保持空著就可以。記得要放進網格的每個元素,都必須是網格容器的直接子元素。

另外我們介紹一個新的語法:repeat() 函數。這是一種簡寫方式,可以幫你快速建立多個重複的網格軌道。像這樣寫:grid-template-rows: repeat(4, auto),就等於是你寫了 auto auto auto auto,意思是總共四行,並且每個軌道高度會依照內容自動調整大小。

你也可以用 repeat() 來定義一種重複模式。舉例來說,repeat(3, 2fr 1fr) 會重複 2fr 1fr 這個組合三次,結果就會是六個欄位:2fr 1fr 2fr 1fr 2fr 1fr

CSS Grid Repeat

我們可以將先前的例子改寫成這樣:

1
2
3
4
5
6
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr); /* repeat function */
grid-template-rows: repeat(2, 1fr);
gap: 0.5em;
}

定位:用數字編號定位網格線 Numbering grid lines

定義網格軌道後,我們將每個網格項目放置到特定位置上。瀏覽器為網格中的每條網格線指派編號,如下圖。 CSS 使用這些數字來指派每個項目應放置的位置

CSS Grid Repeat

用「網格編號」來指定每個項目要放在網格的哪個位置,這時候會用到 grid-columngrid-row 這兩個屬性。

比如說,如果你想讓某個項目跨越從第 1 條到第 3 條的垂直網格線,那就可以寫成:grid-column: 1 / 3。這樣它就會橫跨兩個欄位。同樣的道理,如果你想讓一個項目從第 3 條到第 5 條的水平網格線之間延伸,就可以寫成:grid-row: 3 / 5

這兩個屬性加起來,就能準確地指定一個項目要佔據的網格區域。

1
2
3
4
.card {
grid-column: 1 / 3;
grid-row: 3 / 5;
}

grid-columngrid-row 其實都是簡寫屬性。像 grid-column 其實是 grid-column-startgrid-column-end 的合併寫法,grid-row 也是一樣。只有在用簡寫的時候,才需要用斜線 / 來分隔起始和結束的位置。而那個斜線前後要不要加空格都可以的,純粹看你排版習慣。

Flexbox vs Grid

很多開發者常常會問:Flexbox 不是跟 Grid 很相似嗎?

其實不是,它們不是競爭對手,而是互補的工具,而且它們幾乎是一起被設計出來的。雖然有些功能會重疊,但在不同的情境下,它們各有強項。到底什麼時候該用 Flexbox、什麼時候該用 Grid?可以從這兩個核心差異來判斷:

  • Flexbox 是一維的,只能在「橫的」或「直的」方向上排列;
  • Grid 是二維的,可以同時處理橫向和縱向的排列。

還有一個觀念可以幫助理解:Flexbox 是由內而外,從內容出發,它會根據項目的大小來決定怎麼排版;而 Grid 則是由外到內,先規劃好版型,再把內容填進去,比較像你先畫好格子,再把東西放進去。

如果只是要排列一組類似的東西,比如導覽列、按鈕列,那用 Flexbox 會比較直覺;但如果你要建立一個整體版型,比如整個網頁的區塊結構,Grid 就會更適合,因為它能讓你精準對齊不同區塊。

Flexbox 是從內容向外排版,而 Grid 是從排版結構向內安排內容

Flexbox 很適合用來排列一整行或一整列的元素,而且你不需要特別設定它們的寬度或高度,因為每個項目的大小會依照它的內容自動調整。簡單來說,就是內容有多少,空間就給多少。

Grid 的做法就不太一樣了。你是先定好整個版面怎麼切格子 (幾欄、幾列) ,再把內容塞進去。當然,內容還是會影響格子的大小,但這個變動會影響整個列或整欄,也就是說,一個格子的內容變多,會影響跟它同一列或同一欄的其他項目。

但像是導航選單這種比較不需要精確對齊的項目,其實可以讓內容來決定寬度。比方說文字多的連結寬一點、文字少的就窄一點,這就是典型的「一維水平佈局」。在這種情況下,用 Flexbox 會比 Grid 更合適。

如果你的設計需要在橫向和縱向兩個方向上都安排項目,那就選 Grid;但如果你只需要在單一方向上排列項目,像是一列或一行,那就用 Flexbox 比較合適。

實際上,在大多數情況下 (雖然不一定總是這樣) ,Grid 比較適合用來處理整體版型的架構,例如整個頁面的區塊配置;而Flexbox 則很適合用來處理這些區塊裡面的內容排列,例如按鈕列、選單、圖片組合等等。

注意:無論是 Grid 還是 Flexbox,都可以防止子項目之間的邊界重疊 (Margin Collapsing),這點跟平常使用的區塊排版不太一樣。不過有時候,瀏覽器預設樣式會讓區塊之間看起來空隙太大,可以重新設定瀏覽器預設樣式 margin: 0,來避免間距過多的問題。

好用語法

到這邊為止,我們已經把 Grid 的基本概念都跑過一輪了。接下來要介紹另外兩種寫法:命名網格線 (Naming grid lines)命名網格區域 (Naming grid areas)

這兩種方式其實沒有誰比較好,主要看你的習慣。根據設計的情況,有時候用其中一種會比較直觀、比較容易看懂。

命名網格線 (Naming grid lines)

網格會有很多欄、很多行,單靠數字來追蹤每一條網格線,會讓人眼花撩亂。為了讓這件事變簡單一點,我們可以幫網格線取名,這樣就不需在記數字,換成語意清楚的名稱來表示位置。做法很簡單:在你定義網格行或列的時候,把名稱放在中括號裡。

1
grid-template-columns: [start] 2fr [center] 1fr [end];

也就是建立一個有兩欄的網格,中間會有三條垂直的網格線,分別叫做 startcenter、和 end。之後在放項目的時候,就可以這樣寫:

1
grid-column: start / center;

這樣就代表這個項目從 start 線開始,到 center 線結束,比起寫 1 / 2,更容易理解。

另外也可以給同一條網格線設定多個名稱

1
2
3
4
grid-template-columns: 
[left-start] 2fr
[left-end right-start] 1fr
[right-end];

這樣的設定第 2 個網格線,可以同時有兩個名字:left-endright-start。你在放項目的時候,想用哪個名稱都可以。

更厲害的是,如果你使用 -start-end 這種命名方式,瀏覽器會自動把這兩個之間的區域理解成一個區塊。也就是說,當你定義了 left-startleft-end,就等於創造了一個叫做 left 的區域。之後就可以這樣寫:

1
grid-column: left;

那元素就會從 left-start 跨到 left-end,非常直覺!我把完整範例放在下方:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.container {
display: grid;
grid-template-columns:
[left-start] 2fr
[left-end right-start] 1fr
[right-end];
grid-template-rows: repeat(4, [row] auto);
}
.item1 {
grid-column: left-start / right-end;
grid-row: span 1;
}
.item2 {
grid-column: left; /* 直接指定區域 */
grid-row: row 3 / span 2; /* `row 3` 代表第三個 row 的網格線 */
}

這個範例是用命名網格線來把每個項目放進對應的網格中。而且這個寫法還蠻有趣的:它在 repeat() 函數裡面也加上了水平網格線名稱。

這樣每一條水平網格線 (除了最後一條) 都會被命名。雖然一開始看起來有點怪怪的,但其實同一個名字可以重複使用,這在語法上是完全沒問題的。

row 3 代表從第 3 條同樣命名為 row 的水平網格線開始,然後 span 2 代表與下方軌道合併。

命名網格線的用法其實非常靈活,可以根據自己的排版需求來決定怎麼用。每個網格的結構不同,用法當然也會有所變化。

CSS Grid Naming

這個範例是一種重複的兩欄網格模式,重點在於在每對欄位前面命名網格線。

1
grid-template-columns: repeat(3, [col] 1fr 1fr);

這表示會重複三次這個組合:在一條叫 col 的網格線後面放兩欄。也就是說,你會有三組欄位,每組前面都有一條 col 的命名網格線。接下來,可以這樣放置元素:

1
grid-column: col 2 / span 2;

這行的意思是:從第二個名為 col 的網格線開始,然後橫跨兩欄。

命名網格區域 (Naming grid areas)

命名網格區域是另一種放置網格項目的方式,這種寫法更直觀,你不用去計算格子的編號,也不用命名一堆網格線,而是直接用區塊的名字來安排版面。做法很簡單:

  • 在網格容器上使用 grid-template-areas,來劃分出每個區域的位置。
  • 然後在每個網格項目上加上 grid-area,指派它要對應到哪個區域。

這種寫法算是一種語意化的替代語法,有時候會讓整體結構更容易看懂,特別是對大型版型來說。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
.container {
display: grid;
grid-template-areas:
"title title"
"nav nav"
"main aside1"
"main aside2";
grid-template-columns: 2fr 1fr;
grid-template-rows: repeat(4, auto);
}
.title {
grid-area: title;
}
.nav {
grid-area: nav;
}
.main {
grid-area: main;
}
.aside1 {
grid-area: aside1;
}
.aside2 {
grid-area: aside2;
}

grid-template-areas 這個屬性可以在 CSS 裡用一種像是「ASCII」的方式排版,把整個網格畫出來。

我們將字串加上雙引號,每字串就代表網格裡的一行,然後用空格分隔每一個區域位置。看起來就像你在 CSS 裡畫出一塊區域。第一行可能整排都寫成 title title,代表整行都是標題;第二行寫成 nav nav 放導覽選單;接下來兩行,左邊的格子用 main,右邊分別用 aside1aside2,就能準確表示每個區塊位置。然後你只要在對應的項目加上:

1
grid-area: main;

這樣元素就會自動被放到 main 那一格去。不用再比對數字和線名,這種方式適合固定、清楚分布的版型設計,用來快速看懂整體排版很方便。

注意:每個命名網格區域都必須是個矩形。你不能用它來畫出像 L 形、U 形這種不規則的區塊,這是不被允許的。

如果你想要在某些地方留空白區域,也很簡單,只要在 grid-template-areas 的字串裡用句點 . 當作空白格子就可以。舉個例子,像下方這樣寫法:

1
2
3
4
grid-template-areas:
"top top right"
"left . right"
"left bottom bottom";

這段程式碼定義了四個區塊 (toprightleftbottom) ,中間那個格子留空,是用句點表示的。這樣的視覺結構看起來也很清楚,一眼就知道每個區域的位置。

總結一下:當你在做 Grid 佈局時,可以選擇三種方式來安排元素位置:

  • 編號網格線 Numbering Grid Lines
  • 命名網格線 Naming Grid Lines
  • 命名網格區域 Naming Grid Areas

這三種方式都沒有對錯,選你最順手的就好。但個人比較喜歡使用命名區域,因為比較直觀清楚。

顯式和隱式網格 Explicit and implicit grid

有時候你不一定知道每個項目應該放在網格的哪個位置,像是,你可能有一大堆項目要處理,一個一個手動指定位置很麻煩,又或者這些項目是從資料庫來的,數量根本不確定。在這種情況下,與其指定每個位置,不如大致定義網格結構,然後讓瀏覽器自動幫你擺好,這時候我們就會用到所謂的「隱式網格」 (implicit grid)

你平常用 grid-template-columnsgrid-template-rows 來定義網格時,其實是在建立一個「顯式網格」 (explicit grid),就是你指定哪一欄、哪一列。

但如果有項目超出你定義的欄或列,瀏覽器就會自動幫你新增額外的軌道,也就是所謂的隱式軌道,這樣整個網格就會自動擴展來容納那些額外的項目。

如果某個項目被放到沒有事先定義的格子外面,瀏覽器就會自動幫你新增隱式網格,讓這個項目有地方可以放。它會一直加軌道,直到能夠完整容納這個項目為止。

CSS Grid Layout

上圖的網格只有明確定義一個軌道,但當有項目跑到「第二行」的位置時,瀏覽器就自動新增一條行軌道,來裝下多出來的元素。

在預設情況下,隱式網格軌道的大小是 auto,意思是它們會自動根據內容大小來調整。

不過,如果你想要更精確地控制這些隱式軌道尺寸,可以在網格容器上加上 grid-auto-columnsgrid-auto-rows,像這樣:

1
grid-auto-columns: 1fr;

這樣一來,所有自動新增的欄位都會套用這個大小。

隱式網格軌道不會影響負數網格線的行為。就算有隱式軌道,你用負數 -1 來對齊,它還是會從顯式網格的最底部或最右邊開始算,不會變。

攝影作品集

接下來,我們要實作一個新的頁面,來看看隱式網格在實務上有多好用!這次的範例是一個攝影作品集頁面,最後會像這樣:

CSS Grid Portfolio

我們只定義欄位 (columns) ,每一行都讓瀏覽器自動產生 (也就是隱式網格)。這樣好處是:你不需要知道有幾張圖,不管是 3 張、10 張還是 100 張,網格都會自動幫你排好。每當照片需要換行,就自動新增一行,完全不用手動調整。首先建立我們的 HTML,圖片是找線上隨機產生的圖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div class="portfolio">
<figure class="featured">
<img src="https://picsum.photos/seed/a/400/400" alt="random image" />
</figure>
<figure>
<img src="https://picsum.photos/seed/b/200/200" alt="random image" />
</figure>
<figure class="featured">
<img src="https://picsum.photos/seed/e/400/400" alt="random image" />
</figure>
<figure>
<img src="https://picsum.photos/seed/c/200/200" alt="random image" />
</figure>
<figure>
<img src="https://picsum.photos/seed/d/200/200" alt="random image" />
</figure>
<figure>
<img src="https://picsum.photos/seed/f/200/200" alt="random image" />
</figure>
</div>

我們用 .portfolio 的元素來當作網格容器,裡面放了一系列 <figure>,每個 <figure> 都有一張圖片 <img>,這些 <figure> 就是網格項目。其中幾項上加上了 .featured 的 class,我們會讓它們在版面上看起來比其他的圖大一點,做出視覺重點。

我們分幾個步驟來完成作品集的排版:一開始,先定義好網格軌道,讓所有圖片整齊排列;接著,再把 .featured 放大;最後做一些調整和收尾,讓整體看起來更完整。

我們使用 grid-auto-rows,把所有隱式網格行高都設成 1fr,也就是每一行的高度都一樣,平均分配。這段 CSS 也引入了兩個新概念:auto-fillminmax() 函數,我等等再詳細解釋它們的用法。現在,先把清單裡的 CSS 加進去,我們再來看看這些新語法是怎麼用的。

CSS Grid Portfolio

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
body {
background-color: #f1dbba;
font-family: Helvetica, Arial, sans-serif;
}
.portfolio {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
grid-auto-rows: 1fr;
grid-gap: 1em;
}
.portfolio > figure {
margin: 0;
}
.portfolio img {
max-inline-size: 100%;
}

有時候你不想幫網格軌道設定固定寬度,而是希望它在某個最小值跟最大值之間變動,這時候就可以用 minmax() 函數。這個函數的寫法是 minmax(最小值, 最大值),它會告訴瀏覽器:「這個軌道的大小不能小於某個值,也不能大於某個值」。舉例來說,如果你寫: minmax(200px, 1fr)。那瀏覽器就會保證每個欄位至少有 200px 寬,如果還有空間,就讓它們彈性變寬,最多佔 1fr 的比例空間。

另外還有一個關鍵字叫 auto-fill,是用來搭配 repeat() 函數的特別值。它的意思是:「根據你設定的寬度限制,自動塞滿整個容器能放下的欄數」。也就是說,如果你這樣寫:grid-template-columns: repeat(auto-fill, minmax(200px, 1fr))。瀏覽器就會自動放入盡可能多的欄位,而且每一欄最少寬 200px,最多寬不超過 1fr,這樣每個欄位就會平均分配空間,而且不會太小。

這種搭配很好用,尤其在做 RWD (響應式網頁) 時,可以讓你的格子根據螢幕自動調整數量與大小,不需要寫一堆 @media query

如果螢幕寬度剛好能放下 4 欄、每欄 200px 寬的網格,瀏覽器就自動幫你建立了 4 個欄位。如果螢幕更寬,能放更多,瀏覽器就會多加幾欄;反過來,如果螢幕比較窄,那欄位數也會自動變少。

但要注意:使用 auto-fill 的時候,有可能會出現空的網格欄位。比方說,你的畫面可以放 6 欄,但實際上只有 4 個項目,那剩下的 2 欄就會變成空的格子,還是照樣被排出來。

CSS Grid Auto Fill vs Auto Fit

如果你不想出現這種空的格子,那可以改用 auto-fit 來取代 auto-fillauto-fit 的意思是:「把有內容的欄位拉寬來填滿整個容器」,這樣就不會留下空的欄位,看起來更緊湊。

  • auto-fill:你會得到預期數量的欄位大小,不管有沒有東西填進去;
  • auto-fit:會讓有內容的欄位自動撐開,讓整個容器看起來是滿的,不會有多餘空格。

用哪一個,取決你是想要固定欄數,還是想要讓畫面填滿得漂亮。

增加變化:將 .feature 放大

接下來,我們可以視覺效果更吸引人一點:把 .feature 圖片放大。目前每個網格項目都只佔 1x1 的格子,也就是一欄一列。現在我們要讓 .featured 的項目放大到佔兩欄、兩列,也就是 2x2 的區塊。用 .featured 來鎖定這些項目,然後讓它們在水平和垂直方向上各跨兩個軌道,就能達成放大的效果。

CSS Grid Porfolio

不過這樣做會遇到一個小問題:當你讓某些項目變大時,根據項目的順序,有可能會在網格中產生空隙 (如右上角)。因為有些項目原本預期只佔一格,突然變大後可能會擠到別的項目,結果就出現一些空白。

在這個圖裡,因為網格裡的第 3 個項目放大了,要佔兩格寬和高,所以沒辦法塞進右上角的空間裡,結果它就被擠到下一行。為什麼會這樣?因為當你沒特別指定每個項目的位置時,瀏覽器會使用內建的「網格項目自動放置演算法 (Grid item placement algorithm)」來自動幫你排列。

這個演算法的預設行為是:按照 HTML 裡項目的順序,從左到右、從上往下放。但如果某個項目太大,放不下當前這一行,它就會被移到下一行,去找能放得下的空間。所以在這個例子中,第三個項目就被移到下方,因為那邊的位置才夠。

不過好消息是:CSS Grid 有提供一個屬性叫做 grid-auto-flow,可以用來控制這個排版邏輯。預設值是 row,就是我們剛剛說的「先按列再換行」;如果你設定成 column,那它就會變成「先按行再換列」;更進一步,你可以加上 dense,像這樣:

1
grid-auto-flow: row dense;

這樣做的效果是:演算法會盡量把小的項目「補進」那些因為大項目被跳過的位置。就算會讓項目呈現的順序和原本 HTML 的順序不完全一樣,它也會盡可能把版面填滿,不留空白。

現在,我們來更新一下樣式表:

1
2
3
4
5
6
7
8
9
10
11
.portfolio {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
grid-auto-rows: 1fr;
gap: 1em;
grid-auto-flow: dense;
}
.portfolio .featured {
grid-row: span 2;
grid-column: span 2;
}

這段樣式使用了 grid-auto-flow: dense,其實等同於寫成 grid-auto-flow: row dense,因為 row 是預設值,就算省略也一樣有效。接著,鎖定 .featured 項目讓它在水平方向和垂直方向上各跨兩格,也就是:

CSS Grid Porfolio

注意這個範例只用 span 關鍵字來設定大小,並沒有明確指定項目要放在哪一格。這樣的寫法可以讓瀏覽器的「網格項目自動放置演算法自行決定這些項目要排在哪裡,不用特別指定位置,也避免出現錯位或衝突。

根據你螢幕寬度,你看到的畫面可能會跟上圖完全不一樣。這是因為我們用了 auto-fill 來決定垂直網格軌道的數量。如果你的螢幕比較寬,就能容納更多欄;如果螢幕比較窄,就只會顯示比較少的欄位。

我自己是用大約 1000px 左右的視窗來截圖,所以畫面上剛好出現了 4 欄。如果調整瀏覽器的寬度,網格就會自動適應空間變化,這就是 Grid 的厲害之處!

當你使用 grid-auto-flow: dense 時,畫面上項目的排列順序,可能跟 HTML 原始碼的順序不一樣。對大多數使用者沒差,但對鍵盤 (像是按 Tab 鍵) 或用螢幕閱讀器的使用者來說可能會有點混亂,因為這些輔助工具是根據原始碼的順序來瀏覽,不是照畫面上看到的順序。所以如果你很在意無障礙體驗 (accessibility) ,在使用 dense 這個選項時要特別小心,確保不會造成使用上的混淆。

微調

現在我們已經完成一個複雜的網格佈局,而且還不需要指定每個項目位置,瀏覽器自動幫你排好,非常省力。不過最後還有一個小問題要解決:你會發現有些圖片放大 (.featured項目) 並沒有完全填滿它所在的網格格子,導致下面或右邊多出一點小空隙。理想情況下,同一個網格軌道裡的所有項目,上緣和下緣都應該要對齊,這樣版面才整齊。目前我們的圖片上緣對齊沒問題,但下緣就有點歪。

CSS Grid Porfolio

我們來把空隙修正一下,還記得每個網格項目其實是一個 <figure> 元素,裡面有一張圖片:

1
2
3
<figure class="featured">
<img src="..." alt="monkey" />
</figure>

預設情況下,每個 <figure> (也就是網格項目) 會自動撐滿它被分配到的整個格子,但網格項目裡的子元素 (像圖片) 並不會自動撐滿整個高度。結果就是:<figure> 佔滿了格子,但 <img> 還是保留原來的大小,所以下面就出現一段空白。

要修正這種情況,我們可以用 Flexbox 來解決圖片下方出現空隙的問題。我們可以把每個 <figure> 設成一個彈性容器,接著,對圖片設定 flex-grow,這樣它就會撐滿剩下的空間,不會是原本圖片的高度,下面也就不會有空白了。

但這樣會遇到另一個問題:圖片被強行拉伸的時候,寬高比例會跑掉,導致變形。CSS 有一個專門處理圖片拉伸的屬性叫做 object-fit。預設情況下,<img>object-fitfill,也就是整張圖片會撐滿 <img> 這個框框,不管比例對不對。如果你想讓圖片保持原來的比例,那可以改用這兩個設定:

  • cover:讓圖片放大到填滿整個容器,即使有一部分被裁掉也沒關係。
  • contain:讓整張圖片完整顯示在容器裡,但容器裡可能會留一些空白。

下圖展示這些效果的差別,選擇哪一種取決於你希望圖片被裁切掉,還是要完整顯示。

CSS Grid Image Object Fit

這裡有一個很重要的觀念要釐清:<img> 有兩個層次的尺寸要分開看:

  • 一個是外框大小,也就是 <img> 元素本身的 widthheight
  • 另一個是圖片內容大小,透過 object-fit 設定圖片在框裡面怎麼呈現。

預設情況下,這兩者大小是相同的。但透過 object-fit 可以控制圖片在框裡的呈現方式,而不會因此改變框的大小。

我們剛才用 flex-grow 讓圖片伸展去填滿整個網格格子,然後再加上 object-fit: cover,讓圖片避免拉扯變形。cover 會讓圖片保持比例,並撐滿整個外框,雖然會裁掉圖片的邊角,但這是我們在這裡所做的取捨。

1
2
3
4
5
6
7
.portfolio > figure {
display: flex;
}
.portfolio img {
flex: 1;
object-fit: cover;
}

CSS Grid Porfolio

Subgrid

前面範例中,我們的網格結構都只侷限在 DOM 的兩個層級內:一個網格容器 (parent) 加上它的直接網格項目子元素 (children) 。不過在某些設計中,你可能會遇到更複雜的需求,像是:你需要對齊不在同一個父層底下的元素;或者你希望兩個元素,雖然不在同一層,卻能有一樣的大小或對齊方式。舉個例子:你可能想要讓兩個相同祖先的內容區塊,都能垂直對齊或寬度一致,無論內容多少。

CSS Grid Subgrid

在這個設計裡,每張卡片本身就是網格的一部分,所以它們會整齊地排成一列,並且高度一致。但不只這樣,卡片裡面的內容也會彼此對齊。像有些卡片的標題比較短,這時就會在上方加一些空間,讓它跟標題比較長的卡片對齊;每段內文的開頭也都對齊在同一高度,最底下的「Read More」連結也都整齊排在同一條水平線上。要做到內層與外層內容同步對齊的效果,最理想的方法是用新的 CSS 功能:子網格 (subgrid)

子網格的好處是:可以在一個網格容器裡再建立一個內層網格,並且讓這個內層網格的項目直接對齊外層網格的線條。這樣就可以讓整體看起來更一致。

在這個頁面中,每張卡片會跨三行,而卡片裡的標題、段落、按鈕也剛好對應到那三行的位置。不管是卡片本身還是卡片裡的內容,都是依照同一組網格線排列。

CSS Grid Subgrid

子網格 Subgrid 是一個比較新的 CSS 功能,已經支援大多數瀏覽器。你可以到這個網站查看最新的支援狀況:https://caniuse.com/css-subgrid

我們先建立一個最基本的卡片和樣式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<div class="author-bios">     
<div class="card">
<h3>Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Header</h3>
<div>
<p>
Lorem Ipsum
</p>
</div>
<div class="read-more"><a href="#">Read more</a></div>
</div>
<div class="card">
<h3>Header</h3>
<div>
<p>
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum
</p>
</div>
<div class="read-more"><a href="#">Read more</a></div>
</div>
<div class="card">
<h3>Header</h3>
<div>
<p>
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.
</p>
</div>
<div class="read-more"><a href="#">Read more</a></div>
</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
body {
background-color: #eee;
font-family: Helvetica, Arial, sans-serif;
}
.author-bios {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1em;
max-inline-size: 800px;
margin-inline: auto;
}
.card {
padding: 1rem;
border-radius: 0.5rem;
background-color: #fff;
}
.card > h3 {
margin-block: 0;
padding-block-end: 0.5rem;
border-block-end: 1px solid #eee;
}
.read-more {
margin-block-start: 1em;
text-align: right;
}

CSS Grid Subgrid

現在樣式加上去後,已經接近你想要的設計。不過,還差最後一步:我們來用子網格,讓卡片裡的內容也能跟整個網格對齊一致。

要在頁面上套用子網格,需要做兩件事:先在每個網格項目 (也就是卡片) 上加上 display: grid,這樣就能在每張卡片裡建立一個內層網格;然後,在內層網格的 grid-template-rowsgrid-template-columns 上設定 subgrid,這表示:這個內層網格要使用父層網格的網格來對齊

這樣一來,卡片裡的內容 (標題、文字、按鈕) 就會直接對齊其他卡片內容,達到完全一致的視覺效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.card {
display: grid;
gap: 0;
grid-row: span 3;
grid-template-rows: subgrid;
padding: 1rem;
border-radius: 0.5rem;
background-color: #fff;
}
.card > h3 {
align-self: end;
margin-block: 0;
padding-block-end: 0.5rem;
border-block-end: 1px solid #eee;
}

因為子網格是比較新的功能,有些瀏覽器可能還不支援。如果你想要更保險一點,也可以用 CSS 的功能查詢 (feature query) 來判斷瀏覽器是否支援 subgrid,像這樣寫:

1
2
3
@supports (grid-template-rows: subgrid) {
/* 只有支援 subgrid 的瀏覽器才會套用這段樣式 */
}

另外也可以用 grid-template-columns: subgrid 來對齊列 (columns),但前提是父層有定義網格。你也可以列和欄一起使用 subgrid,讓內部區塊完整對齊父容器。

更進一步,你還可以把這個結構一層一層往下套,也就是在一個子網格裡面再建立另一個子網格,讓整個 DOM 結構中的區塊都能保持一致的網格對齊,非常適合用在大型或模組化的設計裡。

subgrid 中,有一個很棒的特性是:你可以繼承父網格的所有線號 (line numbers) 、線名稱 (line names) 和區域名稱 (grid area names) ,這代表你可以直接用這些資訊來擺放子網格中的元素。舉個例子,假設你想要把卡片中的標題全部放在第二行,你可以這樣寫:

1
2
3
.card > h3 {
grid-row: 2;
}

除了可以繼承父網格的線名稱,你也可以在 subgrid 裡定義自己的線名稱,即使父網格沒有那些名稱:

1
2
3
.card {
grid-template-rows: subgrid [line1] [line2] [line3] [line4];
}

這樣就能使用自訂的線名稱來放置元素。grid-row: line3 / line4; 這表示你要把項目放在第 3 條和第 4 條橫向網格線之間的位置。

Masonry

補充小知識,未來 CSS 可能會加入新功能叫做「masonry layout (瀑布流排版) 」,這種排版方式在圖片集 (像 Pinterest) 裡很常見,但目前要靠 JavaScript 才能實現。

CSS Grid Masonry

Masonry 的特色是:它會把項目放在等寬的欄位中,但每個項目的高度可以不一樣,排版就不會要求每一列都整齊對齊,能更靈活地呈現不同大小的內容。

你可以到這個網站查看最新的支援狀況 https://caniuse.com/?search=masonry

對齊屬性

在 CSS Grid 模組中,我們會用到一些跟 Flexbox 很像的對齊屬性,另外還多了幾個新的。我們其實已經在 Flexbox 介紹過大部分,但有一些屬性是只有在 Grid 裡才有作用的。當你需要更細緻地控制網格排版時,這些對齊屬性就會派上用場。

首先是三個 justify 開頭的屬性,這些是用來 控制水平方向 (橫向) 的排列。接著是三個 align 開頭的屬性,用來控制垂直方向 (直向) 的排列

CSS Grid Alignment Property

最後補充一點:CSS 還提供了簡寫屬性,可以同時設定 align-*justify-* 的值:

  • place-content:同時設定 align-contentjustify-content
  • place-items:同時設定 align-itemsjustify-items
  • place-self:同時設定 align-selfjustify-self

Reference

CSS Flexbox 深入淺出

Flexbox 正式的名稱又稱為 Flexible Box Layout (彈性框排列),是一種在頁面上排列元素 (element) 的方法,它主要用於將元素排列成行或列,也為歷史上困擾已久的垂直居中和等高列的問題提供了一個簡單的解決方案。要說 flexbox 的缺點,那就是它提供的選項數量太多了。

其實不需要把所有新的屬性都學會才能使用 flexbox,因為通常會使用的就那幾個,其他的屬性主要是用來調整對齊方式和元素之間的間距。

這是我們的 Outline:

  • Flexbox 原理
  • Flex item 調整彈性項目
  • Flex Direction 排列方向
  • Flex 參數資料整理

Flexbox 原理

從我們熟悉的 display 屬性開始的。當你在一個元素上設置 display: flex 時,它就會變成一個「彈性容器」 (flexbox container),而它的直接子元素則會變成「彈性項目」 (flexbox item)。預設情況下,這些彈性項目會橫向排列,從左到右排成一行;彈性容器會像 block 區塊元素撐滿可用的寬度,不過裡面的彈性項目不一定會填滿整個容器的寬度,另外它們的高度通常一樣,主要由內容的大小決定。

CSS Flexbox Principle

Note: 你也可以使用 display: inline-flex,這會讓元素變成一個彈性容器,不過它的行為更像是 inline-block,而不是一般的 block 區塊元素。它會跟其他內聯 (inline) 在一行上,不會像區塊元素那樣自動拉到 100% 寬度。至於裡面的彈性項目,表現基本上和 display: flex 是差不多的。實際上不太常會用到這個設定。

這些彈性項目會沿著一條叫「主軸」 (Main Axis) 的方向排列,這條軸是從主軸起點 (Main Start) 延伸到主軸終點 (Main End),方向從左到右。主軸垂直的方向叫做「交叉軸」(Cross Axis),是從交叉軸起點 (Cross Start)到交叉軸終點 (Cross End),方向由上到下。Flexbox 屬性一致使用 start 和 end 術語。

導覽列範例 Menu Bar

我們想要建立一個的選單,大部分的項目會靠左排列,只有其中一個項目在右邊:

CSS Flexbox Image

1
2
3
4
5
6
7
<ul class="site-nav">
<li><a href="/">Home</a></li>
<li><a href="/features">Features</a></li>
<li><a href="/pricing">Pricing</a></li>
<li><a href="/support">Support</a></li>
<li class="nav-right"><a href="/about">About</a></li>
</ul>

在建立這個選單時,你需要先想清楚哪個元素要設為彈性容器。注意它的子元素會自動變成彈性項目。我們下方的範例,應該把 <ul> 設為彈性容器,而它裡面的 <li> 就是彈性項目。把瀏覽器預設的清單樣式取消掉,並加上一些顏色設定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.site-nav {
display: flex; /* 設定 flexbox */
padding: 0.5rem;
list-style-type: none;
background-color: #eee;
}

.site-nav > li > a {
display: block;
padding: 0.5em 1em;
background-color: #bbb;
color: black;
text-decoration: none;
}

在這個範例裡,選單項目的內邊距 padding 是加在裡面的 <a> 標籤上,而不是直接加在 <li> 上。這是因為在點擊時,選單連結的區域都能像連結一樣運作,如果 padding 加在 <li> 上,則 <li> 看起來有一個按鈕,但只有裡面那一小塊 <a> 是能點的。

我們把連結設為 block 元素。這是因為如果還是維持 inline 狀態,那 <a> 對父元素高度的影響只會來自行高 line-height,而不會包含 padding 的高度。但在這裡,我們希望能用 padding 來調整連結區域的大小。

我們要在選單之間加點間距。雖然可以用 margin 辦到,不過 Flexbox 有個更方便的屬性叫 gap,專門用來處理這種情況,比如設定 gap: 1rem,就能在每個彈性項目之間加入 1rem 的間距。此外,Flexbox 也支援用 margin: auto 來填滿項目之間所有剩下的空間,這個技巧也可以用來把最後一個選單項目推到最右邊。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.site-nav {
display: flex;
gap: 1.5rem;
padding: 0.5rem;
list-style-type: none;
background-color: #eee;
}

.site-nav > .nav-right {
margin-inline-start: auto;
}

.site-nav > li > a {
display: block;
padding: 0.5em 1em;
background-color: #bbb;
color: black;
text-decoration: none;
}

margin-inline-startinline 代表的是文字書寫方向,這裡指的是水平的方向,也就是寬度。start 代表的是書寫方向的起點,以我們的例子來說就是左邊。

調整彈性項目 Flex item

在調整 Flexbox 元素大小時,雖然你還是可以用我們熟悉的 widthheight 屬性,不過 flexbox 提供的調整方式比單靠這兩個屬性還要靈活得多。接下來我們要看看其中一個非常實用的屬性:flex (MDN Flex)。這個屬性能控制彈性項目在主軸方向上的大小(通常是寬度)。

1
2
3
4
<div class="flexed">
<div class="column-main">Header</div>
<div class="column-sidebar">Sidebar</div>
</div>

我們將內容分為兩列:左側是主欄,右側是側欄。如果還沒做任何事情來指定兩列的寬度,因此它們會根據其內容自然地調整大小。彈性項目的 flex 屬性為您提供了多種選擇,我們先來看最基本的範例熟悉它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.flexed {
display: flex;
gap: 1.5rem;
}
.column-main {
flex: 2;
}
.column-sidebar {
flex: 1;
}

/* add some color */
.flexed > div {
background-color: #eee;
height: 100px;
text-align: center;
}

CSS Flexbox Image

使用 .column-main.column-sidebar 來定位列,分別套用 flex 2/3 和 1/3 的寬度。這兩個欄位會自動延伸來填滿整個區域,主欄的寬度會是側欄的兩倍。這就是 Flexbox 的厲害之處,它能幫你處理這些計算的細節。

flex 這個屬性,其實是三個屬性的縮寫:flex-growflex-shrinkflex-basis。在這個例子裡,我們只設定了 flex-grow,其他兩個就會採用預設值(分別是 1 和 0%)。所以像 flex: 2 這樣的寫法,其實等同於 flex: 2 1 0%。雖然這種縮寫形式很常用,但如果需要更細的控制,你也可以分別設定這三個屬性。

1
2
3
flex-grow: 2;
flex-shrink: 1;
flex-basis: 0%;

我們分別介紹這些屬性,先從第三個 flex-basis 開始,因為另外兩個會基於 flex-basis 的結果來做調整。

Flex basis: 設定項目大小 (主軸方向)

flex-basis (MDN Flex Basis) 是用來設定元素沿主軸方向的大小。你可以用任何寬度的單位來設定 flex-basis,像是 px、em 或百分比等。

flex-basis初始值 initialauto,如果是 auto 的話瀏覽器會先看當前元素有沒有設定寬度 width。如果有,就使用 width 的值;如果沒有,就依照內容自動決定大小。

也就是說,當你給一個元素設定了非 autoflex-basis 值,原本的寬度 width 設定就會被忽略。

CSS Flexbox Basis

Flex grow: 設定項目放大權重

當每個彈性項目有了 flex-basis 之後,它們有時還需要放大或縮小,以便適應或填滿彈性容器在主軸上的空間。這時候,就輪到 flex-grow (MDN Flex Glow) 和 flex-shrink 發揮作用了。

當每個彈性項目的 flex-basis 被計算出來後,這些項目加上它們之間的 marginpadding,總寬度會達到一個固定值。但這個總寬度不一定剛好填滿整個彈性容器,通常會剩下一些空間。這些多出來的空間,會依照每個彈性項目的 flex-grow 值來分配。這些值都是非負整數。如果某個項目的 flex-grow 是 0,那它就不會比它原本的 flex-basis 更大;但如果某些項目的 flex-grow 是非零的,那它們就會開始擴展,直到把所有剩下的空間都填滿為止。這樣一來,彈性項目就能完全撐滿容器的寬度。

CSS Flexbox Grow

設定較高的 flex-grow 值,等於是賦予這個元素更多的「權重」,它就會拿走剩餘空間中更大的一部分。比如,一個 flex-grow: 2 的項目,會比一個 flex-grow: 1 的項目多擴展一倍的寬度。

CSS Flexbox Grow

這個設定正是我們先前的範例。簡寫寫法 flex: 2flex: 1 表示 flex-basis0%,也就是說整個容器寬度的 100% 都算作剩餘空間(扣掉兩欄之間的 1.5rem 間隙)。然後這些剩下的空間會被分配給兩個欄位:第一欄拿到三分之二,第二欄則拿到剩下的三分之一。

flex 跟大多數簡寫屬性 (shorthand property) 不同的是,這裡如果你省略了一些值,它們不會被還原成初始值,而是會自動套用一些實用的預設值:也就是 flex-grow: 1flex-shrink: 1flex-basis: 0%。這些預設通常就是你需要的設定。這種情況就會建議你使用 flex 的簡寫屬性,而不是只單獨設定 flex-grow

Flex shrink: 設定項目縮小權重

flex-shrink (MDN Flex Shrink) 屬性的作用和 flex-grow 類似。當彈性項目的 flex-basis 確定後,如果這些項目的總寬度超出了彈性容器的可用空間,就可能會出現溢位 (overflow)。

CSS Flexbox Shrink

這時候,每個項目的 flex-shrink 值就決定它是否應該縮小來避免溢出。如果某個項目的 flex-shrink 設為 0,那它就不會縮小。而如果設為大於 0 的數值,它就會縮小,直到不再超出容器。值越高的項目會比值低的項目縮小得更多,縮小的比例是根據 flex-shrink 的值來分配的。同時也會考慮原本的大小,所以當權重相同時,大元素通常會比小元素縮得更多。

在頁面佈局中,這提供了另一種調整欄位寬度的方法。你可以設定每欄的 flex-basis 為所需的百分比(例如 66.67% 和 33.33%),這樣它們加上中間的 1.5rem 間隙總寬度就會多出 1.5rem。然後將兩欄的 flex-shrink 都設為 1,這樣它們就會各自縮小一點來適應容器的寬度。

1
2
3
4
5
6
.column-main {
flex: 66.67%; /* 等同於 flex: 1 1 66.67% */
}
.column-sidebar {
flex: 33.33%; /* 等同於 flex: 1 1 33.33% */
}

這是一種不同的做法,但一樣能有效地達到和之前相同的效果。(跟使用 flex-grow 的方式相比,會有一些微小的差別,使用 flex-shrink 的例子左邊欄位會大一點點)

Flex 範例與各種變化

你可以用很多種方式來運用 flex 屬性。就像我們在頁面上做的那樣,可以透過 flex-growflex-basis 來設定欄位的比例。你也可以設定某些欄是固定寬度,而其他欄則根據視窗大小變化,形成流動欄位 (fluid column)。

CSS Flexbox Example

第三個例子展示的是以前常被稱作「聖杯佈局」版型 (Holy Grail Layout),這在早期的 CSS 裡是非常難實現的一種排版方式。在這個佈局中,左右兩側的欄位寬度是固定的,而中間那欄是「流動的」,也就是說它會自動擴展來填滿剩下的空間。最特別的是,這三個欄位的高度會自動一致,依內容而定。

Flex Direction

Flexbox 裡另一個很重要的功能,就是可以改變排列項目的軸向。你可以透過設定彈性容器的 flex-direction 屬性來控制這一點。它的預設值是 row,讓項目沿著橫向排列,也就是沿著內聯方向排列。如果你改成 flex-direction: column,那項目就會垂直堆疊,也就是沿著區塊方向排列。Flexbox 同時還支援 row-reverse,可以讓項目從右往左排,或是 column-reverse,讓項目從下往上排。

CSS Flexbox Direction

內聯方向 (inline direction)是書寫時的方向。英文是由左到右書寫,因此內聯方向代表著寬度 width。但中文可能由上到下書寫,則內聯方向代表高度 height。區塊方向 (block) 則是跟書寫垂直的方向。

對齊、間距、其他參數

Flex 提供了各式各樣的設定。通常來說,這些步驟可以幫你把元素大致排到想要的位置:

  • 先找出要當作容器的元素,然後在它上面加上 display: flex,讓它變成彈性容器
  • 如果需要的話,可以在容器上設定 gap 或是改變排列方向flex-direction
  • 接著在需要控制大小的彈性項目上設定 flex 屬性

之後如果需要再微調,你可以再加上其他屬性。

彈性容器參數表格:

CSS Flexbox Cheat Sheet

彈性項目參數表格:

CSS Flexbox Cheat Sheet

彈性容器參數

Flex Wrap

flex-wrap 屬性可以讓彈性項目換行,也可以讓它們排成多行。這個屬性有三個值可以設:nowrap(預設值)、wrapwrap-reverse。如果開啟了換行,項目就不會再根據它們的 flex-shrink 值來縮小,而是會直接換到新的一行,避免擠不下。
如果彈性方向是 columncolumn-reverse,那開啟 flex-wrap 會讓項目換到新的一列。不過,只有當容器的高度有設定上限時才會換列,否則容器會自己變高,以容納所有的彈性項目。

Flex Flow

flex-flowflex-directionflex-wrap 的簡寫。例如,flex-flow: column wrap 就表示彈性項目會從上往下排列,必要的時候會換行,排到新的那一列上。

Justify Content

當彈性項目沒有填滿整個容器時,justify-content 屬性可以用來控制它們在主軸上的排列方式。它支援幾個常用的值:flex-startflex-endcenterspace-betweenspace-aroundspace-evenly

預設的 flex-start 會把項目排在主軸的起點,也就是從左邊開始排,而且彼此之間不會有間距,除非你有加 gap 或設定 marginflex-end 則是把項目排在主軸的尾端,看起來會集中在右邊。startend 是邏輯值,會根據書寫模式來決定項目對齊左邊還是右邊,不管主軸方向為何;leftright 是絕對的,永遠左對齊或右對齊,跟書寫模式和主軸方向無關。

如果你想讓項目之間平均分配空間,有三種方式可選。space-between 是把第一個項目貼齊主軸起點,最後一個貼齊主軸終點,其他項目平均分布在中間;space-around 會在第一個和最後一個項目外側加上間距,不過這些間距只有中間間距的一半;而 space-evenly 則是所有的間距都相等,兩端外側間距與中間的間距大小一樣。

要注意的是,這些間距是在算完 marginflex-grow 值之後才會套用。所以如果有項目的 flex-grow 不為 0,或是用了主軸方向的 auto margin,那 justify-content 可能不會發揮作用。

Align Content

如果有開啟換行(也就是用了 flex-wrap),這個屬性可以用來控制每一行在容器裡橫向的間距。它支援的值包括:flex-startflex-endstartendcenterstretch(預設值)、space-betweenspace-aroundspace-evenly
這些值的排列方式,其實跟前面提到的 justify-content 很像,只是是用在每一行之間的距離上。

另外還有一個叫 place-content 的屬性,其實就是 align-contentjustify-content 的簡寫。

Align Items

justify-content 是用來控制項目在『主軸』上的對齊方式,而 align-items 則是用來調整項目在『交叉軸』上的對齊方式。
它的預設值是 stretch,也就是讓所有項目自動撐滿容器的高度(橫向排列時)或寬度(直向排列時),這樣可以讓每一列的高度都一樣,看起來更整齊。

不過如果你不想讓項目撐滿,也可以用其他的對齊方式,這些方式其實有點像 CSS 裡的垂直對齊。像是:

  • flex-startflex-end:讓項目靠交叉軸的起點或終點對齊(通常是一行的頂部或底部)。
  • startend:這是根據容器的書寫方向來決定的邏輯對齊方式。
  • self-startself-end:這會根據每個彈性項目的書寫方向來對齊,如果彈性項目跟彈性容器的書寫方向不同,就會跟 startend 會有差異。
  • center:把項目垂直置中對齊。
  • baseline:讓每個項目中文字的第一行基線對齊(基線就是字的底部那條線)。這在你有些項目字體比較大、有標題,而其他項目是一般小字時特別有用。

另外,你也可以指定對齊的是「第一行」或「最後一行」的基線,比如用 align-items: last baseline,就是讓每個項目中最後一行的文字基線對齊。

justify-contentalign-items 這兩個屬名很容易搞混。可以把它們想像成 Word 裡的文字對齊功能。像是 justify-content 有點像設定文字是靠左、靠右還是左右分散一樣,是用來控制水平方向的排列。
align-items 就像是在做垂直對齊一樣,用來調整內部項目在上下方向上的對齊方式。

place-items 屬性是 align-itemsjustify-items 的簡寫。但 justify-items 只有在 grid 才有用,對於 flexbox 是沒效果的。

彈性項目參數

前面我們已經介紹過 flex-growflex-shrinkflex-basis,還有它們的縮寫 flex。接下來要看的兩個,是跟彈性項目有關的額外屬性:align-selforder

Align Self

align-self 是用來控制單個彈性項目在容器交叉軸上的對齊方式。它的作用跟容器用的 align-items 很像,不同的是,align-self 可以讓你針對每個項目個別設定對齊方式。

預設值是 auto,也就是跟著容器的 align-items 設定。但如果你給它設定別的值,那就會覆蓋容器的對齊設定。

align-self 的值和 align-items 一樣,包括:flex-startflex-endstartendself-startself-endcenterstretch,還有 baseline(基線對齊)。

另外 place-self 屬性,它是 align-selfjustify-self 的簡寫。注意 justify-self 是用在 grid 裡的,在 flexbox 不會有作用。

Order

一般來說,彈性項目的排列順序會跟它們在 HTML 原始碼裡出現的順序一樣,從主軸的起點開始一個接一個排下去。不過,你可以用 order 屬性來改變這個排列順序。

你可以設定任何整數,正的、負的都可以。如果好幾個項目的 order 值一樣,它們之間還是會照 HTML 的順序來排列。預設情況下,所有彈性項目的 order 值都是 0。舉個例子,設成 -1 的項目會被排到最前面,而設成 1 的就會排到後面。你可以自由地給每個項目設定不同的值來調整它們的順序,而且這些值不需要連續的。

注意:雖然 order 很方便,使用上會改變了畫面上的視覺排列順序,但原始碼順序沒改,可能會影響到網站的無障礙體驗。像是用鍵盤(Tab 鍵)切換時,瀏覽器通常還是會按照 HTML 原始碼順序來移動焦點;螢幕閱讀器在讀取時也一樣,大多還是會依照原本的 HTML 順序,這樣可能會讓使用者感到混亂。

Reference