[CSS] Grid Layout
TL;DR
- grid 和 flexbox 有一個關鍵的差異是,grid 需要用 2D 的角度來思考畫面的排版。
Snippets
- Less Absolute Positioning With Modern CSS:透過 CSS Grid 不用使用 position absolute,並做出常見的樣式。
/**
 * Responsive Tiles
*/
.grid-container {
  // 使用 auto-fill 讓它自動決定一行要有幾個 columns
  // 使用 minmax,使得 grid-item 最小至少要 300px,最大則會自己延伸
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  grid-auto-rows: 1fr; // 如果需要讓所有的 gird-item 等高
}
See the Pen Grid Image Tile Layout by PJCHEN (@PJCHENder) on CodePen.
常用屬性
.container {
  display: grid;
  /* 定義 grid 之間的間距 */
  grid-row-gap: 10px;
  grid-column-gap: 20px;
  grid-gap: <grid-row-gap> <grid-column-gap>;
  /* 定義 explicit track */
  grid-template-columns: 100px auto 5rem;
  grid-template-rows: 200px 10px;
  // grid-template 是 grid-template-rows / grid-template-columns 的縮寫
  grid-template: repeat(3, 1fr) / repeat(2, 1fr);
  /* 定義 implicit track */
  grid-auto-rows: 500px;
  grid-auto-columns: 300px;
  /* 定義多餘的元素要以什麼方式排列 */
  grid-auto-flow: column | row(default) | dense;
  /* 定義 grid-area 的名稱 */
  grid-template-areas: 'foo bar';
  /* 對齊與置中 */
  /*
     start | end | center |
     baseline | first baseline | last baseline |
     flex-start | flex-end | left | right |
     space-between | space-around | space-evenly |
     stretch(default);
   */
  align-items: baseline | stretch(default); // no left, right
  justify-content: left | right | stretch(default); // no baseline
  align-content: start | end | center | stretch(default);
  place-content: <align-content> <justify-content>;
  /* repeat (times, unit) */
  grid-template-columns: repeat(4, 1fr); /* 1fr 1fr 1fr 1fr */
  /* minmax 搭配 auto-fit */
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.grid-item {
  /* 定義該 item 要佔據幾個 column 或 row */
  grid-column-start: 1;
  grid-column-end: 4;
  grid-row-start: 1;
  grid-row-end: 3;
 // grid-column 是 grid-column-start / grid-column-end 的縮寫
  grid-column: 2 / span 2; // 從 column 2 開始算起,佔據 2 個 column
  grid-column: span 3; // 從現在的位置算起,佔據 3 個 column
  grid-column: 1 / 3; // 從 column 1 開始到 column 3
  grid-column: 1 / -1; // width: 100%
  grid-row: span 2; // 從現在的位置算起,佔據 2 個 row
  // row-start / column-start / row-end / column-end
  grid-area: 1;   // 等同於 1/auto/auto/auto
  grid-area: 1 / 1 / 2 / 3;
  /* 套用 grid-area 的名稱*/
  grid-area: foo;
  /* 對齊與置中 */
  align-self: start | end | center | stretch(default);
  justify-self: start | end | center | stretch(default);
}
目錄
[TOC]
參考
- Get Started with Grid Layout @ Grid by Example
- A Complete Guide to Grid @ CSS Tricks
- CSS Grid @ Wes Bos
- CSS Grid Layout Guides @ MDN - Web technology for developers
重要觀念
- 當我們在定義 grid 的時候,是透過定義 grid track (grid cell) 來達到,而不是透過定義 grid-line。但是當我們要放置 grid-item 的時候,則是透過 grid line,而非 grid track。
- grid-auto-flow:預設的情況下,當我們只定義兩欄,但卻有超過兩個 grid-item 時,多出來的 grid-item 會自動換到下一列,這時候我們可以使用 grid-auto-flow: column調整成不換行。
- 使用單位 fr (fraction):在 grid-template-columns或grid-template-rows中,若我們使用的是 % 作為單位時,它會以外層 container 的寬度當作總寬去計算百分比,但它不會把grid-gap的寬度考慮進去,因此使用 % 計算時,常常會超出整個 container 的寬度。這時候我們可以使用新的單位-fr,它會把grid-gap的寬度也算進去之後,剩餘的容器寬度下去做分配,分配的方式不是百分比,而是像 flex box 中的flex-grow和flex-shrink類似,按照每一個 item 所給的fr加以計算。
- 容器高度:在預設 grid-auto-flow: row的情況下,多出來的 grid-item 會自動換到下一行。這時候容器的高度或根據 item 的高度來自動延展,因此若使用grid-template-rows並搭配 fr 為單位時,有時候需要在 container 設定高度,才能有自動擴展的效果。
專有名詞(terms)
| 名詞 | 說明 | 圖示 | 
|---|---|---|
| explicit track | 在 MDN 中稱為 explicit grid line,可以透過 grid-template-column和grid-template-rows來定義。 | 虛線(dashed line) | 
| implicit track | 在 MDN 中稱為 implicit grid line,可以透過 grid-auto-rows和grid-auto-columns來定義。 | 點線(dotted line) | 
| gap | 在 MDN 中稱為 gutter 或 alleys,可以透過 column-gap,row-gap, 或簡寫的gap來定義。 | 斜虛線(diagonal dashed line) | 
| explicit grid edge | 實線(solid line) | |
| grid track | 兩條線之間的區域。 | |
| grid cell | grid 的最小單位。 | |
| grid area | 多個 grid cell 可以組成 grid area,它一定是方形的,不會是 L 型的。 | 
explicit 和 implicit grid track
Explicit 和 implicit 的差別在於 explicit 是使用者透過 CSS 定義產生的,而 implicit 則是瀏覽器將剩餘的空間自行運用產生的:
- explicit 的 grid 可以透過 grid-template-columns和grid-template-rows來定義,剩餘多出來的 grid 都視為 implicit grid。
- implicit grid 預設的寬度會根據內容的大小來改變,若想改變 implicit grid 的話,可以透過 grid-auto-rows和grid-auto-columns來定義。

grid-container
定義 grid track 的欄寬和列高(grid-template-columns, grid-template-rows)
- px,- rem:直接定義想要的值。
- fr:fraction,指的是 container 扣掉 grid-gap 後的空間加以分配,用法類似- flex-grow和- flex-shrink。
- auto:如果搭配的單位內有- fr這類彈性變化的單位使用,則會以 grid-item 本身原本內容的寬度呈現;如果搭配的只有- px或- rem這類固定的單位,則會自 動填滿所有剩餘的空間(見 [fit-content() 和 auto 的使用](#fit-content() 和 auto 的使用))。
.grid-container {
  grid-template: <grid-template-rows> / <grid-template-columns>; /* 縮寫 */
  grid-template-columns: auto 50px auto;
  grid-template-rows: 10px 10px 10px;
}
定義 grid item 間距的寬度(grid-gap)
.grid-container {
  grid-gap: <grid-row-gap> <grid-column-gap>; /* 縮寫 */
  grid-row-gap: 20px;
  grid-column-gap: 20px;
}
定義 grid item 排列的方式(grid-auto-flow)
.grid-container {
  grid-auto-flow: row(default) / column / dense;
}
當每一個 grid 的 寬度和高度都相同時

當 grid 的寬高有所不同時
.grid-container {
  display: grid;
  grid-gap: 20px;
  grid-auto-flow: dense;
  grid-template-columns: repeat(10, 1fr);
}
.grid-item:nth-child(3n) {
  background-color: cornflowerblue;
  grid-column: span 5;
}
.grid-item:nth-child(5n) {
  background-color: tomato;
  grid-column: span 4;
}
.grid-item:nth-child(7n) {
  background-color: greenyellow;
  grid-row: span 2;
}
- 使用預設的 grid-auto-flow: row;則不考慮 grid-item 的大小,僅依照順序將 grid-item 填入,但若放不進去,則會換行,因此較容易留有空白。
- 使用 grid-auto-flow: dense;則會先盡可能依照大小把可以放入的 grid-item 填入,接著才考慮順序,最後放不下時才換行,較不易出現空白(但不保證一定沒空白),特別適合用在不那麼重視順序(例如,相片牆)的情況。

對齊與置中(justify-content, justify-items, place-items)
- justify-content/- align-content:用來對齊 grid-container 內的 grid-item,適用在以絕對單位定義寬度(例如,- px),使得 grid-container 有多餘的空間時。
- justify-items/- align-items:用來對齊 grid-item 裡面的內容(content)。如果只要針對某個 grid-item ,可以針對該 item 下- align-self或- justify-self。
- place-items:是 align-items 和 justify-items 的縮寫,前者表示 align-items,後者為 justify-items,例如- place-items: center或- place-items: center start;。
- justify-xxx 適用於 x-axis;algin-xxx 適用於 y-axis。
- 使用 align-content 時,最好幫 grid-container 設 height,且不要設定 grid-template-rows,效果會比較明顯(但是通常 grid-container 是不會設定 height 的)。
.grid-container {
  justify-content: start | end | center | stretch(default) | space-around | space-between | space-evenly;
  justify-items: start | end | center | stretch(default);
}
縮寫
.grid-container {
  grid: none | <grid-template-rows> / <grid-template-columns> | <grid-auto-flow> [<grid-auto-rows> [/ <grid-auto-columns>]];
}
.grid-container {
  grid: 200px auto / 1fr auto 1fr;
}
/* 等同於 */
.grid-container {
  grid-template-rows: 200px auto;
  grid-template-columns: 1fr auto 1fr;
  grid-template-areas: none;
}
grid-item
定義要佔 據多少欄或列(grid-column, grid-row)
- grid-column可以用來定義該 item 要佔據幾個 column,預設是- grid-column: span 1。
- 當所設定的 grid-column 欄數過多而無法在該列容納下時,會自動換到下一行。
- 當所設定的 grid-column 欄數超過 grid-template-column所設定的欄數時,會跑出新的 implicit grid column。
使用 span <num> 可以用來定義要佔據幾個:
grid-column: span 2; /* 該 grid 會佔據 2 個欄寬 */
也可以透過 grid-column-start 和 grid-column-end 定義該 grid 要從第幾個 track 開始到第幾個 track 結束:
.grid-item {
  /* short hand: grid-column: 3 / 5; */
  grid-column-start: 3; /* 從 track 3 開始 */
  grid-column-end: 5; /* 到 track 5 結束 */
}
也可以同時使用 span <num> 和 grid-column short hand:
.grid-item {
  /* 表示這個 grid-item 要佔據兩個欄寬,並以 track 5 的位置作為結束 */
  grid-column: span 2 / 5;
  /* 表示這個 grid-item 要佔據三個欄寬,並以 track 2 的位置作為開始 */
  grid-column: 2 / span 3;
  /* 表示這個 grid-item 要從,並以 track 2 的位置作為開始,track 5 的位置作為結束 */
  grid-column: 2 / 5;
  /* 表示這個 grid-item 將佔據整個 row ,類似 width: 100% */
  grid-column: 1 / -1;
}
排序(order)
使用 order 可以為 grid-item 重新排序,但要留意的是,沒使用 order 的 grid-item 會排在最面,接著才是有使用 order 的。另外,order 只是顯示時的順序改變,在瀏覽器的 DOM 並沒有真的改變它的位置,因此在
.grid-item {
  order: 1;
}
對齊與置中(justify-self, align-self)
- justify-self / align-self:用來針對單個 gird-item 中的 content 進行對齊。如果要改變所有 grid-items ,可以在 grid-container 中使用 align-items和justify-items。
.grid-item {
  align-self: start | end | center | stretch(default);
  justify-self: start | end | center | stretch(default);
}
grid function
repeat(times, unit)
grid-template-columns: repeat(4, 1fr); /* 1fr 1fr 1fr 1fr */
grid-template-columns: repeat(2, 1fr 2fr); /* 1fr 2fr 1fr 2fr */
grid-template-columns: 100px repeat(2, 1fr) 200px; /* 100px 1fr 1fr 200px */
auto-fill & auto-fit
auto-fill 和 auto-fit 都會自動根據 grid-container 的寬度調整欄數並換行,但是當 item 的數目不足以換行時,兩者的表現會有些微差異:
- auto-fill:當 grid-container 還有空間時,會繼續往後延伸新的 grid。
- auto-fit:當 grid-container 還有空間時,不會繼續往後延伸新的 grid。
repeat(auto-fill, 100px)
repeat(auto-fit, 100px)

minmax()
當我們做 RWD 時,常會希望 grid-item 中的項目可以隨著 container 自動延展,但卻又希望它的大小至少不要小於某一個值的時候,經常會使用到 minmax(min, max) 這個 function,第一個參數表示最小值,第二個參數表示最大值:
/**
 * 搭配 auto
 * minmax(100px, auto) 中的 auto 表示它會「根據內容的尺寸」來調整大小。
**/
.grid-container {
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: minmax(100px, auto); /* 最小 100px,最大根  據內容尺寸 */
}
/**
 * 搭配 fr
**/
.grid-container {
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: minmax(150px, fr); /* 最小 150px,否則大家分配相同的空間 */
}
minmax() 經常搭配 auto-fit 或 auto-fill 使用,做出不同的效果:
/**
 * 搭配 auto-fit 時,因為當 grid-container 還有空間時,不會繼續往後延伸新的 grid。
 * 因此 grid-item 會根據 grid-container 的寬度佔滿整個 grid-container
**/
.grid-container {
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
/**
 * 搭配 auto-fill 時,因為當 grid-container 還有空間時,會繼續往後延伸新的 grid。
 * 因此 grid-item 不會自動填滿整個 grid-container 的寬度,
**/
.grid-container {
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}

fit-content() 和 auto 的使用
auto
- 
當 grid-template-columns裡面的單位全都是絕對單位(例如,px),則使用auto時該 gird-item 會自動延展 grid-container 中剩餘的空間。
- 
當 grid-template-columns裡面的單位有彈性單位(例如,fr),則使用auto時該 gird-item 會根據 grid-item 的內容自動展開(不會讓 grid-item 換行)。

fit-content()
透過 fit-content 可以讓 grid-item 的內容一定不會超出 grid-item 的寬度(需要時會自動換行)
- 當 fit-content()內給的值比 grid-item 中的內容寬小時,會自動換行,並縮小到可縮的最小寬度。
- 當 fit-content()內給的值比 grid-item 中的內容寬大時,效果會和有給 auto 和彈性長度時的效果一樣。

其他
為 grid track 命名:grid-template-areas & grid-area
可以在 grid-container 中透過 grid-template-areas 定義每一個 grid-area (i.e., cell) 的名稱;接著可以在 grid-item 中選擇套用的 grid-area ,該 grid-item 會自動填滿所定義的 grid-area。
使用上可以類似 flex-box 中的 order,當在不同的裝置尺寸下時(搭配 @media),套用不同的 grid-template-area,即  可重新定義整個 layout。
最好能先清楚透過 grid-template-columns 和 grid-template-rows 定義每個 grid-area。
/**
 * grid-template-areas 套用在 grid-container 上
 * 透過雙引號包住一個 row
 * 如果有不想命名的 grid-area 可以使用 "." 代替
**/
.grid-container {
  grid-template-columns: 1fr 500px 1fr;
  grid-template-rows: 150px 150px 100px;
  grid-template-areas:
    'sidebar1 content sidebar2'
    'sidebar1 content sidebar2'
    'footer footer footer';
}
/**
 * grid-area 套用在 grid-item 上
 * 指定對應到的名稱後,該 grid-item 會填滿整個 grid-area
**/
.grid-item.sidebar1 {
  grid-area: sidebar1;
}
.grid-item.sidebar2 {
  grid-area: sidebar2;
}
.grid-item.content {
  grid-area: content;
}
.grid-item.footer {
  grid-area: footer;
}
為 grid-area 命名後,也可以在 grid-item 中透過 xxx-start 和 xxx-end 選擇它作為開始和結束的指標:
/* 可以直接在 grid-item 中使用定義好的名稱,不需加上雙引號 */
.grid-item {
  grid-column: foo-start / bar-end;
}
為 grid-line 命名:grid-template-columns, grid-template-rows
如果需要為 grid-line 命名,也可以在 grid-container 的 grid-template-columns 或 grid-template-rows 中使用 [] 來定義 grid-line 的名稱,接著就可以直接在 grid-item 中的 grid-column 或 grid-row 使用。
/* 在 grid-container 中使用 [] 定義 track 名稱 */
.grid-container {
  display: grid;
  grid-gap: 20px;
  grid-template-columns: [site-left] 1fr [content-start] 500px [content-end] 1fr [site-right];
  grid-template-rows: [content-top] repeat(3, auto) [content-bottom];
}
/* 可以直接在 grid-item 中使用定義好的名稱,不需加上雙引號 */
.grid-item.item3 {
  background-color: steelblue;
  grid-column: content-start / content-end;
  grid-row: content-top / content-bottom;
}
實際應用
建立 Grid Layout
grid-system-layout @ frontend masters
.grid-container {
  display: grid;
  grid-gap: 20px;
  grid-template-columns: repeat(10, 1fr); /* 建立每列有 10 個 column 的 grid */
}
.grid-item {
  grid-column: span 2; /* 定義該 item 要佔據幾個 column */
}
.w-100 {
  grid-column: 1 / -1; /* 該 item 會佔據整列 */
}