跳至主要内容

[CSS] Grid Layout

TL;DR

  • grid 和 flexbox 有一個關鍵的差異是,grid 需要用 2D 的角度來思考畫面的排版。

Snippets

/**
* 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]

參考

重要觀念

  • 當我們在定義 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-columnsgrid-template-rows 中,若我們使用的是 % 作為單位時,它會以外層 container 的寬度當作總寬去計算百分比,但它不會把 grid-gap 的寬度考慮進去,因此使用 % 計算時,常常會超出整個 container 的寬度。這時候我們可以使用新的單位-fr,它會把 grid-gap 的寬度也算進去之後,剩餘的容器寬度下去做分配,分配的方式不是百分比,而是像 flex box 中的 flex-growflex-shrink 類似,按照每一個 item 所給的 fr 加以計算。
  • 容器高度:在預設 grid-auto-flow: row 的情況下,多出來的 grid-item 會自動換到下一行。這時候容器的高度或根據 item 的高度來自動延展,因此若使用 grid-template-rows 並搭配 fr 為單位時,有時候需要在 container 設定高度,才能有自動擴展的效果。

專有名詞(terms)

名詞說明圖示
explicit track在 MDN 中稱為 explicit grid line,可以透過 grid-template-columngrid-template-rows 來定義。虛線(dashed line)
implicit track在 MDN 中稱為 implicit grid line,可以透過 grid-auto-rowsgrid-auto-columns 來定義。點線(dotted line)
gap在 MDN 中稱為 gutter 或 alleys,可以透過 column-gap, row-gap, 或簡寫的 gap 來定義。斜虛線(diagonal dashed line)
explicit grid edge實線(solid line)
grid track兩條線之間的區域。
grid cellgrid 的最小單位。
grid area多個 grid cell 可以組成 grid area,它一定是方形的,不會是 L 型的。

explicit 和 implicit grid track

Explicit 和 implicit 的差別在於 explicit 是使用者透過 CSS 定義產生的,而 implicit 則是瀏覽器將剩餘的空間自行運用產生的:

  • explicit 的 grid 可以透過 grid-template-columnsgrid-template-rows 來定義,剩餘多出來的 grid 都視為 implicit grid。
  • implicit grid 預設的寬度會根據內容的大小來改變,若想改變 implicit grid 的話,可以透過 grid-auto-rowsgrid-auto-columns 來定義。

Imgur

grid-container

定義 grid track 的欄寬和列高(grid-template-columns, grid-template-rows)

  • px, rem:直接定義想要的值。
  • fr:fraction,指的是 container 扣掉 grid-gap 後的空間加以分配,用法類似 flex-growflex-shrink
  • auto:如果搭配的單位內有 fr 這類彈性變化的單位使用,則會以 grid-item 本身原本內容的寬度呈現;如果搭配的只有 pxrem 這類固定的單位,則會自動填滿所有剩餘的空間(見 [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 的 寬度和高度都相同時

Imgur

當 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 填入,接著才考慮順序,最後放不下時才換行,較不易出現空白(但不保證一定沒空白),特別適合用在不那麼重視順序(例如,相片牆)的情況。

Imgur

對齊與置中(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-selfjustify-self
  • place-items:是 align-items 和 justify-items 的縮寫,前者表示 align-items,後者為 justify-items,例如 place-items: centerplace-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-startgrid-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-itemsjustify-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-fillauto-fit 都會自動根據 grid-container 的寬度調整欄數並換行,但是當 item 的數目不足以換行時,兩者的表現會有些微差異

  • auto-fill:當 grid-container 還有空間時,會繼續往後延伸新的 grid。
  • auto-fit:當 grid-container 還有空間時,不會繼續往後延伸新的 grid。
repeat(auto-fill, 100px)
repeat(auto-fit, 100px)

Imgur

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-fitauto-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));
}

Imgur

fit-content() 和 auto 的使用

auto

  • grid-template-columns 裡面的單位全都是絕對單位(例如, px ),則使用 auto 時該 gird-item 會自動延展 grid-container 中剩餘的空間。

  • grid-template-columns 裡面的單位有彈性單位(例如, fr ),則使用 auto 時該 gird-item 會根據 grid-item 的內容自動展開(不會讓 grid-item 換行)。

Imgur

fit-content()

透過 fit-content 可以讓 grid-item 的內容一定不會超出 grid-item 的寬度(需要時會自動換行)

  • fit-content() 內給的值比 grid-item 中的內容寬小時,會自動換行,並縮小到可縮的最小寬度
  • fit-content() 內給的值比 grid-item 中的內容寬大時,效果會和有給 auto 和彈性長度時的效果一樣。

Imgur

其他

為 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-startxxx-end 選擇它作為開始和結束的指標:

/* 可以直接在 grid-item 中使用定義好的名稱,不需加上雙引號 */
.grid-item {
grid-column: foo-start / bar-end;
}

為 grid-line 命名:grid-template-columns, grid-template-rows

如果需要為 grid-line 命名,也可以在 grid-containergrid-template-columnsgrid-template-rows使用 [] 來定義 grid-line 的名稱,接著就可以直接在 grid-item 中的 grid-columngrid-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 會佔據整列 */
}

參考