[CSS] 關於 Grid Layout 的使用姿勢

CSS Grid 其實已經不是什麼很新的東西,只是最近看到有一個實驗性功能,被 Chrome 正式版本採用,覺得好像應該來寫一個筆記文來記錄一下。順便當作是小小的教學文章也可以,反正 CSS 對我來說也從來沒有真正熟悉過。

Grid 網格系統

CSS 自從 Level 3 之後,後續所有發展的項目,都有屬於自己的 Level。所以 CSS Grid 自然就會有自己的一套。正式的名稱叫做 CSS Grid Layout Module Level 1,實際上被提出的時間是在 2012 年左右,還是屬於 CSS Level 3 的新功能。等到他正式被 RC 的時間,已經來到 2017 年年底了。

何謂 網格系統

如果說 display: flex 是屬於一個維度的方向操作,那麼 display: grid 就可以視為是兩個維度的方向操作。簡而言之,這個網格系統提供了一個 的操作概念,當你的操作區塊被定義為網格時,其下的子區塊,就被賦予網格系統的操作特性。

你可以想像一下 Excel 的操作方式,就是有 的那個樣子,然後,要對你的每一個儲存格做操作,而所謂的儲存格,其實就把他想成你的 DOM 元件,差不多是那個樣子。然後 Excel 所擁有的合併儲存格,基本上也是有差不多的作法。

偷偷工商一下,我都看 AMOS 的影片長大的 XD

CSS GRID / CSS格線好好玩【完整版】 | CSS教學 | CSS格線

本篇不會提到關於對齊的相關事宜,只會講容器跟項目之間的事情。如果需要研究對齊,網路上有超多文章可以參考。

Grid Garden 這是小遊戲,可以玩玩看!
格線佈局的基本概念
CSS Grid 网格布局教程

基本概念

在整個網格佈局的概念裡面,最外面的東西叫做容器( Grid Container ),然後這個容器裡面所包含的物件,叫做項目( Grid Item ),基本的組成大概會類似這樣,

紅色的區塊,是屬於 容器 的部分,而中間粉紅色的區塊,則是屬於 項目 的部分。而他的 HTML 的內容很簡單,就是定義出四個區塊而已。為了要讓畫面上比較容易區分,所以我在區塊中間加上了 10px 的間隔,容器本身也給出了 10px 的留白填色,方便辨識。

容器裡面的子項目,就會依照你所定義的規範,以欄列的方式來排列,

與 Excel 一樣,這種欄列排列方式,交叉的地方就可以當作一個單元格,如果你有 3 欄 4 列的容器,那麼你所擁有的項目單元格就會有 3 x 4 = 12 個項目單元。

既然有了單元格,那麼中間就會有所謂的 格線,以上述為例,3 欄 4 列的容器,會有多少格線呢?以水平方向來說,會有 3 + 1 = 4 條水平格線,垂直方向來說,會有 4 + 1 = 5 條垂直格線。

網格容器

容器( Grid Container )的定義很簡單,

display: grid;
/* OR */
display: inline-grid;

打完收工。這樣就是告訴瀏覽器,我是一個網格容器,然後我什麼事都沒做(所以你活該畫面顯示不對)。

特別注意,當一個 DOM 被定義為 grid 時,其內部的子節點會自動被定義為網格項目。而當被定義為網格項目時,他就是屬於網格層級的元件( grid-level boxes ),而並非原有的區塊層級元件( block-level boxes )。

原本的行內層級,會被轉譯為網格層級,例如 display: inline-block, display: table-cell,會被轉譯為 display: block 的網格層級元件。
而以下設定會失效:float, vertical-align, column-*

我們有了容器,就可以開始定義容器的內容該怎麼長。首先,基本定義上我們有兩個,

  • grid-template-columns 定義容器有多少欄
  • grid-template-rows 定義容器有多少列

所以如果要做一個 3x4 的容器,那麼我們可以這樣寫,

#container {
    display: grid;
    grid-template-columns: 100px 100px 100px;
    grid-template-rows: 100px 100px 100px 100px;
}

這樣我們就會有一個項目是 100x100 的 3x4 的容器了。這兩個屬性有多種寫法,目的是相同的,但是有幾種衍生的變化類型。首先,他們可以接受的數值,加上特別關鍵字,有七種可使用的方式,

  1. 任何非負值的單位表示法,例如 100px, 10%, 1rem,與新的 fr 單位。
  2. Flex 佈局單位 fr
  3. minmax(min, max) 格線系統中,以最小尺寸 min 定義項目尺寸,最大尺寸限制需小於等於 max 設定的尺寸。
  4. max-content 格線系統中,取得項目最大的尺寸當作填充條件。
  5. min-content 格線系統中,取得項目最小的尺寸當作填充條件。
  6. auto 自動設定項目尺寸。

所以我們可以寫這種比較複雜的東西,

#container {
    display: grid;
    grid-template-columns: 1fr min-content minmax(100px, max-content);
    grid-template-rows: 100px 100px 100px 100px;
}

請留意 min-content, max-content 的參考值是容器內的項目。

另外,這邊還提供了一個函數 repeat(),讓你可以不用重複寫一樣的設定,例如以現今流行的 12 欄位佈局來說,就這樣設定就好了,

#container {
    display: grid;
    grid-template-columns: repeat(12, 1fr);
}

而這個 repeat() 函數還有兩個特殊關鍵字,

  • repeat(auto-fill, 100px) 利用 100px 的尺寸,自動填滿容器。
  • repeat(auto-fit, 100px) 利用 100px 的尺寸,僅設定所擁有的欄或是列。

這兩個關鍵字的差異可能不明顯,先上圖讓大家瞭解一下差異,

這樣大家看出差異了嗎?如果你的容器比較大,項目本身並無法填滿時,auto-fill 會幫你填滿(即便他是空的項目),而 auto-fit 則只做完你所擁有的項目而已。

接著還有一個比較特別的地方,就是你可以替你的欄或列命名,主要的寫法是這樣,

#container {
    display: grid;
    grid-template-columns: [c1] 100px [c2] 100px [c3] 100px;
    grid-template-rows: [r1] 100px [r2] 100px 100px 100px;
}

前面用中刮號刮起來的地方,就是那個欄或列的名稱,這個名稱可以多值,所以可以寫成 [c1 column_one] 這樣也是可行的。

接著還有間距的設定,目前 gap 系列的參數,已經將 grid- 前綴拿掉了,所以目前只要寫 row-gap, column-gapgap 即可。

#container {
    display: grid;
    row-gap: 5px;
    column-gap: 5px;
    gap: 5px 5px;
}
  • column-gap 設定欄之間的間距尺寸。
  • row-gap 設定列之間的間距尺寸。
  • gap 統一設定欄與列之間的間距,規則是 <row> <column>

接著是 grid-auto-flow 這個屬性,預設的網格排序方式,先欄後列的排序。所以,這個屬性可以改變排列的順序。

  • row 預設的排序方式,先欄後列。
  • column 先排列,後排欄位。
  • dense 自動填滿的關鍵字,他會依照排序方式,盡量填滿容器。

關於 dense 的寫法為,

#container {
  grid-auto-flow: row dense;
  /* OR */
  grid-auto-flow: column dense;
}

最後,還有一個 grid-template-areas 的屬性,是用於定義每一個網格項目的名稱,而每個網格項目可以使用 grid-area 來指定名稱,便可以指定要放到那個位置。

#container {
  grid-template-areas:
    "head head"
    "nav main"
    "footer footer";
}

指定區域名稱,當不使用該區域時,可以使用點( . )來略過他。

#container {
  grid-template-areas:
    ". head"
    "nav main"
    ". footer";
}

網格項目

容器項目( Grid Item )的定義,就是網格系統內的區塊元件。簡單來說,雖然都是 display: block,但是容器項目會有網格系統特有的屬性。

  • grid-column-start 定義欄位開始的格線的位置。
  • grid-column-end 定義欄位結束的格線的位置。
  • grid-row-start 定義列開始的格線的位置。
  • grid-row-end 定義列結束的格線的位置。
  • grid-column 整合 grid-column-startgrid-column-end
  • grid-row 整合 grid-row-startgrid-row-end
  • grid-area 指定項目要放到哪一個位置。

還記得最前面所提到的 格線 嗎?上述這些屬性就是依照格線,來定義你的網格項目要放在什麼位置。而 grid-area 比較特別,除了可以放在某個命名的位置上外,也可以使用格線的位置來指定。

grid-area: <row-start> / <column-start> / <row-end> / <column-end>;

使用方式,

.item {
  grid-area: header;
  /* OR */
  grid-area: 1 / 1 / 3 / 1;
}

display: contents

其實這才是我要分享的東西,上面沒看到都沒關係(欸)。

一圖解千言:

當然他的問題還是很多,但我覺得,如果是應用在大範圍的結構上,還是多少有一點點好處。

More accessible markup with display: contents
Display: Contents Is Not a CSS Reset
Vanishing boxes with display contents

那麼實際上他到底做了什麼事情?簡單的來說,如果你使用 Flex 或是 Grid 容器,當你的項目當中的子元件,如果設定了 display: contents,那麼,那一個子元件將會從原有的結構樹中移除,然後僅保留內容,如果你的父元件是 Flex 或是 Grid 容器,那麼這個元件就會變成容器項目的一員。

僅只是 內容 被留下,其他原有套用的背景什麼的會失效,所以被移出去這件事情是瀏覽器做的事,但是你的 DOM 還是沒變。但是,你使用 Chrome DevTool 去看該元素時,畫面不會幫你框出來(燦笑)。

實際操作可以參考這一篇文章:

Why display: contents is not CSS Grid Layout subgrid

但目前 CSS Grid Layout 還是在吵 subgrid,也就是子項目的網格系統。就先靜觀其變吧。

小結

醫院真不是寫文章的地方。

Hina Chen
偏執與強迫症的患者,算不上是無可救藥,只是我已經遇上我的良醫了。
Taipei