上集介紹的 Flex 排版系統幫我們解決了很多過往排版上的麻煩,甚至可以試著在不用設置 media query 的情況下就達到 RWD。而 Grid 排版則是更方便我們去掌握整個 二維排版的 layout,擁有上帝一般?)的排版魔法,在使用過後不禁驚嘆:Oh My Grid!
Oh My Grid
啟用方式:
只要加上 display: grid 之後,該元素內的子元素都會變成 grid 子元素。
.container{
display: grid;
}
使用 grid-template-columns/rows 設定格線欄/列
grid-template-columns
利用 grid-template-columns
來設定格線上的 column,以下例子設定有兩個 column,第一個 column 寬 100px,第二個 column 寬 200px
這個寬度的單位可以是 rem, px, % 等,也可以是之後會提及的 fr。
grid-gap
而使用 grid-gap
可以為每個欄列之間加上空隙,要注意,超過的內容會直接凸出來:
<body>
<div class="container">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
</div>
<style>
.container {
display: grid;
grid-gap: 20px;
grid-template-columns: 100px 200px;
}
<style>
</body>
grid-template-row
利用 grid-template-row
來設定格線的 row,第一個 row 為設定為長度 100px、而第二個 row 長度為 200px,這裡的長是指「row 的長度」,在絕對意義上來說就是 height,至於 column 的長度則自動延展的,由於 HTML 中有四個子元素,但只設定了兩個 row,所以第三個 row 和第四個 row 是自動產生的 row(implicit rows):
.container {
display: grid;
grid-gap: 20px;
grid-template-rows: 100px 200px;
}
grid-template
grid-template
可以結合 grid-template-column
和 grid-template-row
。
.container {
display: grid;
grid-gap: 20px;
grid-template: 1fr 1fr / 1fr 1fr
}
上面地寫法相當於:
.container {
display: grid;
grid-gap: 20px;
grid-template-column: 1fr 1fr
grid-template-row: 1fr 1fr
}
explicit & implicit 概念
有注意到畫面上的線條嗎?這是使用 firefox developer version 的 dev tools 功能所幫忙畫出來的,實線代表的是我們在 grid-template-rows: 100px 200px
所手動設定出來的格線,稱為 explicit tracks,由 explicit tracks 所設定出來的範圍就叫做一個 explicit grid,就像是圖中的 1 和 2 兩個區塊都在這個 explicit grid 裡面。
而 3 和 4 則在實線之外,被點點虛線圈住,這些點點虛線代表這些格線是自動產生的,不是我們設定的(因為我們只設定了第一個 row 和第二個 row),稱為 implicit tracks,由 implicit tracks,所生成的 row 稱為 implicit rows。
這樣的話,這些不是透過 grid-template-rows 所設定出來的 rows 要怎麼調整長度呢?
使用 grid-auto-rows 設定 implicit rows 的長度
上面的例子中,我們可以利用 grid-auto-rows
來為設定自動產生的 rows 設定長度為 150px:
.container {
display: grid;
grid-gap: 20px;
grid-template-rows: 100px 200px;
grid-auto-rows: 150px;
}
當我們定義出 columns 的數量時,多出來的子元素會「換行」到下一個 row:
.container {
display: grid;
grid-gap: 20px;
grid-template-columns: 100px 200px;
grid-template-rows: 50px 100px;
grid-auto-rows: 150px;
}
就算只設定 grid-template-rows,也代表預設會有一個 column,所以超過 explicit grid 的子元素會跑到自動產生的 row,而我們再用 grid-auto-rows: 150px;
來設定 implicit rows 的長度:
那什麼情況之下可以自動產生 columns 呢?
grid-auto-flow
grid-auto-flow 這個屬性是用來定義當有子元素超過 explicit grid 範圍的時候,要怎麼排列它們,預設的值是 row,所以我們先前的範例中,多出來的子元素才會自動變成下一個 row。
在下面這個例子中,因為設定了 grid-auto-flow: column
,所以多出來的子元素會變成新的一個 column,而這個 column 因為不是在 explicit grid 的範圍,所以需要用 grid-auto-columns: 200px
來調整長度(然而這不是一個實用的範例 XD):
<body>
<div class="container">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">3</div>
<style>
.container {
display: grid;
grid-gap: 20px;
grid-template-columns: 400px 200px;
grid-auto-flow: column;
grid-auto-columns: 200px;
}
</style>
</body>
調整容器內欄列的大小
fr
這裡要介紹一個新的單位 fr
(fractional),就是佔比的意思,有點類似我們在介紹 flex 排版時介紹過的 flex-grow 概念。
當我們把第四個 column 設為 2fr,而前三個設為 1fr 的時候,代表把自由空間各分配 1 份給前三個 column,分配兩份給第四個 column:
當第一個和第二個 column 已經設定了固定的尺寸,第三第四個 column 設定 fr 來分配剩下的自由空間,而第三個 column 拿一份,第四個 column 拿兩份:
.container {
display: grid;
height: 200px;
grid-gap: 20px;
border: 10px solid black;
grid-template-columns: 100px 100px 1fr 2fr;
}
auto
使用 auto 代表依內容決定寬度,要注意和 fr 是不同的概念。
<body>
<div class="container">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
</div>
<style>
.container {
display: grid;
height: 200px;
grid-gap: 20px;
border: 10px solid black;
grid-template-columns: 200px 1fr auto 1fr;
}
</style>
</body>
利用 repeat() 來簡化欄列的寫法
grid-template-columns: 1fr 1fr 1fr 1fr;
使用 repeat()
可以簡化成:
grid-template-columns: repeat(4, 1fr);
第一個參數是要重複幾次,第二個參數是要重複什麼,也可以做這樣複雜的 repeat:
grid-template-columns: 1fr 2fr 1fr 2fr 1fr 2fr 1fr 2fr 1fr 2fr;
使用 repeat()
可以簡化成:
grid-template-columns: repeat(4, 1fr 2fr);
也可以做這樣的組合:
grid-template-columns: 200px repeat(4, 1fr 2fr) auto;
// 有 10 欄,第一欄是 200px,最後一欄依內容決定
設定個別子元素所佔的格線區域:grid-column & grid-row
grid-column & grid-row
為個別的子元素設定 grid-column
和 grid-row
屬性,可以定義該子元素 column 和 row 的範圍,使用的方法是設定要從哪一條格線開始,到哪一條格線結束。可以利用 firefox 的 dev tool 來幫我們搞清楚到底是哪一條線。
在下面的例子中,我們定義 target 要從第一條 explicit track 線開始,到第五條 explicit track 線結束:
.container {
display: grid;
grid-gap: 20px;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(5, 1fr);
}
.target {
background: #BADA55;
grid-row-start: 1;
grid-row-end: 5;
}
也可以設定為 -1 直接代表最尾端的那條 explicit track 線:
.container {
display: grid;
grid-gap: 20px;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(5, 1fr);
}
.target {
background: #BADA55;
grid-row-start: 1;
grid-row-end: -1;
}
因為我們只有設定 5 個 rows,所以最尾端的一條 explicit track 就是標記為 6 的那條實線:
利用 span 跳格子
若使用 span
加上數字,則代表要佔幾格的意思,如下列 CSS 中,item 9 要佔兩個 columns 與兩個 rows:
.container {
display: grid;
grid-gap: 20px;
grid-template-columns: repeat(5, 1fr);
}
.item9 {
background: mistyrose;
grid-column: span 2;
grid-row: span 2;
}
因為預設只有五個 column,如果我們在子元素上設定要跨 10 格 column,多出來的 column 就會超過 explicit grid 的範圍,成為 implicit columns:
.container {
display: grid;
grid-gap: 20px;
grid-template-columns: repeat(5, 1fr);
}
.item9 {
background: mistyrose;
grid-column: span 10;
grid-row: span 2;
}
語法簡化
可以使用簡化語法,以更簡潔地劃分元素所要佔的格線位置。第一個參數是開始的線,第二個參數是結束的線,也可以搭配 span 使用:
.container {
display: grid;
grid-gap: 20px;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(5, 1fr);
}
.target {
background: #BADA55;
grid-row: 1 / -1;
grid-column: 1 / span 2;
}
auto-fill & auto-fit
當我們使用 repeat()
時,可以在第一個參數設定第要重複的次數是 auto-fill
或 auto-fit
。
auto-fill
若是設定 auto-fill,則代表我們要盡可能地在容起裡面放最多的 tracks。
.container {
display: grid;
grid-gap: 20px;
grid-template-columns: repeat(auto-fill, 150px);
}
.item4 {
grid-column-end: -1;
}
auto-fit
若是設定 auto-fill,一樣我們要盡可能地在容起裡面放最多的 tracks,但空白 track 的 size 會被設為0px,並且折疊起來。
.container {
display: grid;
grid-gap: 20px;
grid-template-columns: repeat(auto-fit, 150px);
}
.item4 {
grid-column-end: -1;
}
更詳細的區別可以看這篇介紹
RWD 排版組合技:repeat() & auto-fill/fit & minmax()
上面介紹看起來不是很實用的 auto-fill/fix 就是為了使用這個 RWD 組合技啦。
首先先用 repear()
來重複產生 column,要產生的數量是 auto-fit,就是盡可能產生越多越好。
接著要設定 column 的寬度,使用 minmax()
,意思是最小一定不能小於 150px,最大可以是 1 fr,也就是佔一個自由空間份額。
.container {
display: grid;
grid-gap: 20px;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
當我們改變視窗大小,壓到每個 column 小於 150px 後,該 column 沒辦法 auto-fit 進來所以消失,子元素就被擠到下一個 row 上的 column,而上面三個子元素盡情伸展成 1fr,平攤原本的那條 row 上的空間:
我們繼續壓縮視窗大小,壓到每個 column 小於 150px 後,該 column 沒辦法 auto-fit 進來所以消失,所以原本第一個 row 上又有一個子元素被擠到下一個 row 上,兩條 row 上的子元素都盡情伸展成 1 fr 平攤 row 上的空間:
再一次壓縮視窗大小,直到 grid 容器只容得下一個 column 了,該 column 伸展成 1fr,填滿整個容器:
透過這種方式,我們就可以不設置 media query 也可以輕鬆做到 RWD 惹!
自訂格子和線的名字
為格子命名
使用 grid-template-columns
和 grid-template-rows
畫好格子之後,也可以使用 grid-template-area
為特定的格子命名,並指定要將什麼元素放在此格子裡。
.container {
display: grid;
grid-gap: 20px;
grid-template-columns: 1fr 10fr 1fr;
grid-template-rows: 150px 150px 100px;
grid-template-areas:
"sidebar-1 content sidebar-2"
"sidebar-1 content sidebar-2"
"footer footer footer"
}
.footer {
grid-area: footer;
}
.item1 {
grid-area: sidebar-1;
}
.item2 {
grid-area: content;
}
.item3 {
grid-area: sidebar-2;
}
命名之後的區域,也可以搭配 grid-row
和 grid-column
,直接定義要從哪個命名區塊開始或結束:
.container {
display: grid;
grid-gap: 20px;
grid-template-areas:
"💩 💩 💩 💩 🍔 🍔 🍔 🍔"
"💩 💩 💩 💩 🍔 🍔 🍔 🍔"
"💩 💩 💩 💩 🍔 🍔 🍔 🍔"
"💩 💩 💩 💩 🍔 🍔 🍔 🍔";
}
.item3 {
grid-column: 💩-start / 🍔-end;
grid-row-end: 💩-end;
}
同樣地,如果子元素超出 explicit grid 範圍,將會自動生成 implicit grid 來放置多餘的子元素,就像上面 26~30 的子元素一樣。
為格線命名
可以使用 []
為格線命名,以便之後指定元素在格線上的位置時使用,一條格線可以不只一個命名,有點類似 CSS 的 class,可以有多重命名。
.container {
display: grid;
grid-gap: 20px;
grid-template-columns: [sidebar-start site-left] 1fr [sidebar-end content-start] 500px [content-end] 1fr [site-right];
grid-template-rows: [content-top] repeat(10, auto) [content-bottom];
}
.item3 {
background: slateblue;
grid-column: content-start;
grid-row: content-top / content-bottom;
/* grid-row: 1 / span 10; */
}
使用 grid-auto-flow: dense 解決跳格問題
在預設的情況下,當一列無法擠下我們某個子元素時,就會自動跳到下一列,使用 grid-auto-flow: dense
之後,會跳過該子元素,讓可以 fit 進那些空格的子元素先往上遞補。
.container {
display: grid;
grid-gap: 20px;
grid-template-columns: repeat(10, 1fr);
/* grid-auto-flow: dense; */
}
.item:nth-child(6n) {
background: cornflowerblue;
grid-column: span 6;
}
.item:nth-child(8n) {
background: tomato;
grid-column: span 2;
}
.item:nth-child(9n) {
grid-row: span 2;
}
.item18 {
background: greenyellow !important;
grid-column-end: -1 !important;
}
使用 grid-auto-flow: dense
前:
使用 grid-auto-flow: dense
後:
Grid 的排序與各種對齊方式
怎麼對齊?
在 grid 容器元素上設定 justify-items
可以決定所有的 grid 子元素在水平線(主軸)上怎麼對齊,設定 align-items
可以設定所有的 grid 子元素在垂直線(交叉軸)上要怎麼對齊。
justify-items: start | end | center | stretch
Align-items: start | end | center | stretch
若只想要改變某個 grid 元素的對齊方式,可在子元素上設定 justify-self
即 align-self
。
以下僅列出 justify-items
的用法,大致用法都和 flex 排版的原理相通,更詳細的範例可以看 CSS-TRICKS 上的 complete grid guide
.container {
justify-items: start;
}
.container {
justify-items: center;
}
.container {
justify-items: end;
}
.container {
justify-items: stretch;
}
怎麼分配多餘空間?
用法和 flex 排版相同,利用 justify-content
或 align-content
可以決定 grid 格線裡面的多餘空間要怎麼分配,同樣地,justify 用來決定水平基線的分配而 align 則決定垂直線上的分配
為了幫助理解,也可以想成這是 grid 容器裡的整體子元素的對齊方式。
.container {
justify-content: start;
}
.container {
justify-content: end;
}
.container {
justify-content: center;
}
.container {
justify-content: space-between;
}
.container {
justify-content: space-evenly;
}
.container {
justify-content: space-around;
}
更詳細的範例可以參考 CSS-TRICKS 上的 complete grid guide
怎麼排序?
排序的方法和 flex 排版相同,設定 order
以決定順序,所有子元素的預設 order 是 0。
.container {
display: grid;
grid-gap: 20px;
grid-template-columns: repeat(10, 1fr);
}
.logo {
grid-column: span 2;
order: 2;
}
.nav {
grid-column: span 8;
order: 1;
}
.content {
grid-column: 1 / -1;
order: 3;
}
參考資料及相關資源
這篇文章有許多是上 Wes Bos 的 Free Gird Course所整理的筆記,部分範例也是我自己在課程練習時所截的圖,強烈推薦這門免費課程。
另外 CSS-TRICKS 上的 A Complete Guide to Grid 中的說明和例圖都很簡單易懂,也幫助我釐清許多 Grid 觀念。
中文的資源則推薦 Amos 大的教學影片CSS GRID / CSS格線好好玩【完整版】。
學習完怎麼用 Grid 之後,可以玩玩看這個小遊戲Grid Garden - A game for learning CSS grid來複習並確認自己是不是真的理解了。
忘記 Grid 怎麼用的時候,記得回來看看這篇文章(咦?),或是可以使用 grid cheatsheet 來作弊快速複習。
Flex or Grid?
最後想提一下,在怎麼選擇要使用 Flex 還是 Grid 排版其實並沒有絕對的答案。
在做二維排版時,Flex 排版是把每層都切分好,再去設定每層要怎麼排。而 Grid 排版系統則透過事先規劃好的格線,讓元素在格線上排列,讓我們更方便地掌握整個 layout 的佈局,做到更精準的排版。
但千萬不要把 Flex 和 Grid 當成兩種互斥工具,應該在了解這兩種工具的特性之後,依情況選擇要怎麼使用,或是把 Flex 和 Grid 搭配起來靈活運用,才是使用這些工具的好方法。