CSS Flex & Grid 排版詳解(下):Oh My Grid!


Posted by Nicolakacha on 2020-09-22

上集介紹的 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-columngrid-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-columngrid-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-fillauto-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-columnsgrid-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-rowgrid-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-selfalign-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-contentalign-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 搭配起來靈活運用,才是使用這些工具的好方法。


#css #grid







Related Posts

MTR04_0806

MTR04_0806

邪魔歪道還是苦口良藥?Functional CSS 經驗分享

邪魔歪道還是苦口良藥?Functional CSS 經驗分享

NPM & Node.js & Package.json的關係與使用

NPM & Node.js & Package.json的關係與使用


Comments