CSS Grid 深入淺出
上一篇的 Flexbox 徹底改變了我們在網頁上排版的方式,但它其實只是整個佈局系統 (Layout) 的一部分。它還有個大哥就是所謂的網格佈局模組 (Grid Layout) 。這兩個佈局系統加起來,提供了一套非常完整又強大的佈局工具。
CSS 網格允許你定義出列與行,做出「二維佈局」,然後再把項目放進這個網格裡。有些項目可能只佔一個格子,或是跨好幾列或好幾行,我們可以很精確地設定每個格子的大小,也可以讓它們根據內容自動調整。項目可以精確地放在某個格子裡,也可以讓它們自己流動去填補空位。透過網格,我們可以打造出非常複雜的版面設計,就像下圖。
基礎:Basic Grid
我們先從一個很基礎的範例開始:把六個框排成三列。如下圖:
1 | <div class="grid"> |
就像 Flexbox 一樣,Grid 也是作用在 DOM 的父子層級。當你把某個元素設為 display: grid
,它就變成了「網格容器」 (Grid Container) ,而它底下的子元素就會變成「網格項目」 (Grid Items) 。
1 | .grid { |
我們用了 display: grid
把元素變成了「網格容器」。這個容器的行為就像一般的 block
區塊元素一樣,會自動填滿整個可用寬度。
雖然這裡沒寫到,不過你也可以用
inline-grid
,這會讓元素像inline
行內元素那樣跟文字一起流動,而且寬度只會剛好包住它的子元素。不過實際上,大多數情況還是會用display: grid
,用inline-grid
的機會比較少。
接下來是兩個新的屬性:grid-template-columns
和 grid-template-rows
,用來定義每一列和每一行的大小。
這裡我們用了一個新的單位 fr
,它的意思是「比例單位」,有點像 Flexbox 裡的 flex-grow
。例如,grid-template-columns: 1fr 1fr 1fr
就是建立三個等寬的欄位,每個欄都佔一等份。不一定每一列或每一行都得用 fr
,也可以用其他單位像是 px
、em
或 %
,甚至可以混著用。像這樣:grid-template-columns: 300px 1fr
,意思是第一欄寬度固定 300px
,第二欄則佔據剩下所有可用空間。如果你寫 2fr
,那它的寬度就是 1fr
的兩倍。
還有一個很實用的屬性是 gap
,它是用來設定網格之間的間距,就跟 Flexbox 裡的 gap
一樣用法。你也可以一次設定兩個值,像是 gap: 0.5em 1em
,這樣就能分別指定垂直和水平的間距。
在剛開始定案網格規範的時候,
gap
這個屬性原本叫做grid-gap
,所以可能會在一些早期的範例中看到這個名稱。其實它的功能跟現在的gap
是一樣的。後來 CSS 規範更新了,改為比較通用的gap
,而且連 Flexbox 也一起加入支援,變得更一致好用。
剖析:Grid
想要真正掌握網格系統,就要先搞懂它的各個術語。接下來,在網格系統裡,還有四個術語是一定要知道的:
- 網格線 (Grid Lines) :網格線構成整個網格的結構,可以是垂直的或水平的,分別出現在列或行的兩邊。如果你有設
gap
的話,網格線就會在間距的兩側,或是說間距會出現在網格線的之間。 - 網格軌道 (Grid Tracks) :是指兩條相鄰網格線之間的區域。軌道可以是橫向的 (行) 或直向的 (列) 。
- 網格單元格 (Grid Cells) :網格裡的每一格,簡單來說,就是橫向和直向的軌道交錯出來的空間。
- 網格區域 (Grid Areas) :由一個或多個單元格組成的長方形區塊,範圍是由兩條垂直網格線和兩條水平網格線包起來的。
建立網格佈局的過程中,會不斷參考網格裡的這些基本結構。舉個例子來說,當你寫下 grid-template-columns: 1fr 1fr 1fr
,其實就是定義了三個等寬的垂直「網格軌道」。這樣一來,也就產生了四條垂直的「網格線」:一條在網格線的最左邊、兩條在每個欄位之間,還有一條在最右邊。
Note:你的設計不一定要把網格的每個格子都填滿!如果你有區域想要留空白,只要讓那個格子保持空著就可以。記得要放進網格的每個元素,都必須是網格容器的直接子元素。
另外我們介紹一個新的語法:repeat()
函數。這是一種簡寫方式,可以幫你快速建立多個重複的網格軌道。像這樣寫:grid-template-rows: repeat(4, auto)
,就等於是你寫了 auto auto auto auto
,意思是總共四行,並且每個軌道高度會依照內容自動調整大小。
你也可以用 repeat()
來定義一種重複模式。舉例來說,repeat(3, 2fr 1fr)
會重複 2fr 1fr
這個組合三次,結果就會是六個欄位:2fr 1fr 2fr 1fr 2fr 1fr
。
我們可以將先前的例子改寫成這樣:
1 | .grid { |
定位:用數字編號定位網格線 Numbering grid lines
定義網格軌道後,我們將每個網格項目放置到特定位置上。瀏覽器為網格中的每條網格線指派編號,如下圖。 CSS 使用這些數字來指派每個項目應放置的位置
用「網格編號」來指定每個項目要放在網格的哪個位置,這時候會用到 grid-column
和 grid-row
這兩個屬性。
比如說,如果你想讓某個項目跨越從第 1 條到第 3 條的垂直網格線,那就可以寫成:grid-column: 1 / 3
。這樣它就會橫跨兩個欄位。同樣的道理,如果你想讓一個項目從第 3 條到第 5 條的水平網格線之間延伸,就可以寫成:grid-row: 3 / 5
。
這兩個屬性加起來,就能準確地指定一個項目要佔據的網格區域。
1 | .card { |
grid-column
和 grid-row
其實都是簡寫屬性。像 grid-column
其實是 grid-column-start
和 grid-column-end
的合併寫法,grid-row
也是一樣。只有在用簡寫的時候,才需要用斜線 /
來分隔起始和結束的位置。而那個斜線前後要不要加空格都可以的,純粹看你排版習慣。
Flexbox vs Grid
很多開發者常常會問:Flexbox 不是跟 Grid 很相似嗎?
其實不是,它們不是競爭對手,而是互補的工具,而且它們幾乎是一起被設計出來的。雖然有些功能會重疊,但在不同的情境下,它們各有強項。到底什麼時候該用 Flexbox、什麼時候該用 Grid?可以從這兩個核心差異來判斷:
- Flexbox 是一維的,只能在「橫的」或「直的」方向上排列;
- Grid 是二維的,可以同時處理橫向和縱向的排列。
還有一個觀念可以幫助理解:Flexbox 是由內而外,從內容出發,它會根據項目的大小來決定怎麼排版;而 Grid 則是由外到內,先規劃好版型,再把內容填進去,比較像你先畫好格子,再把東西放進去。
如果只是要排列一組類似的東西,比如導覽列、按鈕列,那用 Flexbox 會比較直覺;但如果你要建立一個整體版型,比如整個網頁的區塊結構,Grid 就會更適合,因為它能讓你精準對齊不同區塊。
Flexbox 是從內容向外排版,而 Grid 是從排版結構向內安排內容
Flexbox 很適合用來排列一整行或一整列的元素,而且你不需要特別設定它們的寬度或高度,因為每個項目的大小會依照它的內容自動調整。簡單來說,就是內容有多少,空間就給多少。
Grid 的做法就不太一樣了。你是先定好整個版面怎麼切格子 (幾欄、幾列) ,再把內容塞進去。當然,內容還是會影響格子的大小,但這個變動會影響整個列或整欄,也就是說,一個格子的內容變多,會影響跟它同一列或同一欄的其他項目。
但像是導航選單這種比較不需要精確對齊的項目,其實可以讓內容來決定寬度。比方說文字多的連結寬一點、文字少的就窄一點,這就是典型的「一維水平佈局」。在這種情況下,用 Flexbox 會比 Grid 更合適。
如果你的設計需要在橫向和縱向兩個方向上都安排項目,那就選 Grid;但如果你只需要在單一方向上排列項目,像是一列或一行,那就用 Flexbox 比較合適。
實際上,在大多數情況下 (雖然不一定總是這樣) ,Grid 比較適合用來處理整體版型的架構,例如整個頁面的區塊配置;而Flexbox 則很適合用來處理這些區塊裡面的內容排列,例如按鈕列、選單、圖片組合等等。
注意:無論是 Grid 還是 Flexbox,都可以防止子項目之間的邊界重疊 (Margin Collapsing),這點跟平常使用的區塊排版不太一樣。不過有時候,瀏覽器預設樣式會讓區塊之間看起來空隙太大,可以重新設定瀏覽器預設樣式
margin: 0
,來避免間距過多的問題。
好用語法
到這邊為止,我們已經把 Grid 的基本概念都跑過一輪了。接下來要介紹另外兩種寫法:命名網格線 (Naming grid lines)和命名網格區域 (Naming grid areas)。
這兩種方式其實沒有誰比較好,主要看你的習慣。根據設計的情況,有時候用其中一種會比較直觀、比較容易看懂。
命名網格線 (Naming grid lines)
網格會有很多欄、很多行,單靠數字來追蹤每一條網格線,會讓人眼花撩亂。為了讓這件事變簡單一點,我們可以幫網格線取名,這樣就不需在記數字,換成語意清楚的名稱來表示位置。做法很簡單:在你定義網格行或列的時候,把名稱放在中括號裡。
1 | grid-template-columns: [start] 2fr [center] 1fr [end]; |
也就是建立一個有兩欄的網格,中間會有三條垂直的網格線,分別叫做 start
、center
、和 end
。之後在放項目的時候,就可以這樣寫:
1 | grid-column: start / center; |
這樣就代表這個項目從 start
線開始,到 center
線結束,比起寫 1 / 2
,更容易理解。
另外也可以給同一條網格線設定多個名稱:
1 | grid-template-columns: |
這樣的設定第 2 個網格線,可以同時有兩個名字:left-end
和 right-start
。你在放項目的時候,想用哪個名稱都可以。
更厲害的是,如果你使用 -start
和 -end
這種命名方式,瀏覽器會自動把這兩個之間的區域理解成一個區塊。也就是說,當你定義了 left-start
和 left-end
,就等於創造了一個叫做 left
的區域。之後就可以這樣寫:
1 | grid-column: left; |
那元素就會從 left-start
跨到 left-end
,非常直覺!我把完整範例放在下方:
1 | .container { |
這個範例是用命名網格線來把每個項目放進對應的網格中。而且這個寫法還蠻有趣的:它在 repeat()
函數裡面也加上了水平網格線名稱。
這樣每一條水平網格線 (除了最後一條) 都會被命名。雖然一開始看起來有點怪怪的,但其實同一個名字可以重複使用,這在語法上是完全沒問題的。
row 3
代表從第 3 條同樣命名為 row 的水平網格線開始,然後 span 2
代表與下方軌道合併。
命名網格線的用法其實非常靈活,可以根據自己的排版需求來決定怎麼用。每個網格的結構不同,用法當然也會有所變化。
這個範例是一種重複的兩欄網格模式,重點在於在每對欄位前面命名網格線。
1 | grid-template-columns: repeat(3, [col] 1fr 1fr); |
這表示會重複三次這個組合:在一條叫 col
的網格線後面放兩欄。也就是說,你會有三組欄位,每組前面都有一條 col
的命名網格線。接下來,可以這樣放置元素:
1 | grid-column: col 2 / span 2; |
這行的意思是:從第二個名為 col
的網格線開始,然後橫跨兩欄。
命名網格區域 (Naming grid areas)
命名網格區域是另一種放置網格項目的方式,這種寫法更直觀,你不用去計算格子的編號,也不用命名一堆網格線,而是直接用區塊的名字來安排版面。做法很簡單:
- 在網格容器上使用
grid-template-areas
,來劃分出每個區域的位置。 - 然後在每個網格項目上加上
grid-area
,指派它要對應到哪個區域。
這種寫法算是一種語意化的替代語法,有時候會讓整體結構更容易看懂,特別是對大型版型來說。
1 | .container { |
grid-template-areas
這個屬性可以在 CSS 裡用一種像是「ASCII」的方式排版,把整個網格畫出來。
我們將字串加上雙引號,每字串就代表網格裡的一行,然後用空格分隔每一個區域位置。看起來就像你在 CSS 裡畫出一塊區域。第一行可能整排都寫成 title title
,代表整行都是標題;第二行寫成 nav nav
放導覽選單;接下來兩行,左邊的格子用 main
,右邊分別用 aside1
和 aside2
,就能準確表示每個區塊位置。然後你只要在對應的項目加上:
1 | grid-area: main; |
這樣元素就會自動被放到 main
那一格去。不用再比對數字和線名,這種方式適合固定、清楚分布的版型設計,用來快速看懂整體排版很方便。
注意:每個命名網格區域都必須是個矩形。你不能用它來畫出像 L 形、U 形這種不規則的區塊,這是不被允許的。
如果你想要在某些地方留空白區域,也很簡單,只要在 grid-template-areas
的字串裡用句點 .
當作空白格子就可以。舉個例子,像下方這樣寫法:
1 | grid-template-areas: |
這段程式碼定義了四個區塊 (top
、right
、left
、bottom
) ,中間那個格子留空,是用句點表示的。這樣的視覺結構看起來也很清楚,一眼就知道每個區域的位置。
總結一下:當你在做 Grid 佈局時,可以選擇三種方式來安排元素位置:
- 編號網格線 Numbering Grid Lines
- 命名網格線 Naming Grid Lines
- 命名網格區域 Naming Grid Areas
這三種方式都沒有對錯,選你最順手的就好。但個人比較喜歡使用命名區域,因為比較直觀清楚。
顯式和隱式網格 Explicit and implicit grid
有時候你不一定知道每個項目應該放在網格的哪個位置,像是,你可能有一大堆項目要處理,一個一個手動指定位置很麻煩,又或者這些項目是從資料庫來的,數量根本不確定。在這種情況下,與其指定每個位置,不如大致定義網格結構,然後讓瀏覽器自動幫你擺好,這時候我們就會用到所謂的「隱式網格」 (implicit grid) 。
你平常用 grid-template-columns
或 grid-template-rows
來定義網格時,其實是在建立一個「顯式網格」 (explicit grid),就是你指定哪一欄、哪一列。
但如果有項目超出你定義的欄或列,瀏覽器就會自動幫你新增額外的軌道,也就是所謂的隱式軌道,這樣整個網格就會自動擴展來容納那些額外的項目。
如果某個項目被放到沒有事先定義的格子外面,瀏覽器就會自動幫你新增隱式網格,讓這個項目有地方可以放。它會一直加軌道,直到能夠完整容納這個項目為止。
上圖的網格只有明確定義一個軌道,但當有項目跑到「第二行」的位置時,瀏覽器就自動新增一條行軌道,來裝下多出來的元素。
在預設情況下,隱式網格軌道的大小是 auto
,意思是它們會自動根據內容大小來調整。
不過,如果你想要更精確地控制這些隱式軌道尺寸,可以在網格容器上加上 grid-auto-columns
或 grid-auto-rows
,像這樣:
1 | grid-auto-columns: 1fr; |
這樣一來,所有自動新增的欄位都會套用這個大小。
隱式網格軌道不會影響負數網格線的行為。就算有隱式軌道,你用負數
-1
來對齊,它還是會從顯式網格的最底部或最右邊開始算,不會變。
攝影作品集
接下來,我們要實作一個新的頁面,來看看隱式網格在實務上有多好用!這次的範例是一個攝影作品集頁面,最後會像這樣:
我們只定義欄位 (columns) ,每一行都讓瀏覽器自動產生 (也就是隱式網格)。這樣好處是:你不需要知道有幾張圖,不管是 3 張、10 張還是 100 張,網格都會自動幫你排好。每當照片需要換行,就自動新增一行,完全不用手動調整。首先建立我們的 HTML,圖片是找線上隨機產生的圖:
1 | <div class="portfolio"> |
我們用 .portfolio
的元素來當作網格容器,裡面放了一系列 <figure>
,每個 <figure>
都有一張圖片 <img>
,這些 <figure>
就是網格項目。其中幾項上加上了 .featured
的 class,我們會讓它們在版面上看起來比其他的圖大一點,做出視覺重點。
我們分幾個步驟來完成作品集的排版:一開始,先定義好網格軌道,讓所有圖片整齊排列;接著,再把 .featured
放大;最後做一些調整和收尾,讓整體看起來更完整。
我們使用 grid-auto-rows
,把所有隱式網格行高都設成 1fr
,也就是每一行的高度都一樣,平均分配。這段 CSS 也引入了兩個新概念:auto-fill
跟 minmax()
函數,我等等再詳細解釋它們的用法。現在,先把清單裡的 CSS 加進去,我們再來看看這些新語法是怎麼用的。
1 | body { |
有時候你不想幫網格軌道設定固定寬度,而是希望它在某個最小值跟最大值之間變動,這時候就可以用 minmax()
函數。這個函數的寫法是 minmax(最小值, 最大值)
,它會告訴瀏覽器:「這個軌道的大小不能小於某個值,也不能大於某個值」。舉例來說,如果你寫: minmax(200px, 1fr)
。那瀏覽器就會保證每個欄位至少有 200px
寬,如果還有空間,就讓它們彈性變寬,最多佔 1fr
的比例空間。
另外還有一個關鍵字叫 auto-fill
,是用來搭配 repeat()
函數的特別值。它的意思是:「根據你設定的寬度限制,自動塞滿整個容器能放下的欄數」。也就是說,如果你這樣寫:grid-template-columns: repeat(auto-fill, minmax(200px, 1fr))
。瀏覽器就會自動放入盡可能多的欄位,而且每一欄最少寬 200px
,最多寬不超過 1fr
,這樣每個欄位就會平均分配空間,而且不會太小。
這種搭配很好用,尤其在做 RWD (響應式網頁) 時,可以讓你的格子根據螢幕自動調整數量與大小,不需要寫一堆 @media query
!
如果螢幕寬度剛好能放下 4 欄、每欄 200px
寬的網格,瀏覽器就自動幫你建立了 4 個欄位。如果螢幕更寬,能放更多,瀏覽器就會多加幾欄;反過來,如果螢幕比較窄,那欄位數也會自動變少。
但要注意:使用 auto-fill
的時候,有可能會出現空的網格欄位。比方說,你的畫面可以放 6 欄,但實際上只有 4 個項目,那剩下的 2 欄就會變成空的格子,還是照樣被排出來。
如果你不想出現這種空的格子,那可以改用 auto-fit
來取代 auto-fill
。auto-fit
的意思是:「把有內容的欄位拉寬來填滿整個容器」,這樣就不會留下空的欄位,看起來更緊湊。
auto-fill
:你會得到預期數量的欄位大小,不管有沒有東西填進去;auto-fit
:會讓有內容的欄位自動撐開,讓整個容器看起來是滿的,不會有多餘空格。
用哪一個,取決你是想要固定欄數,還是想要讓畫面填滿得漂亮。
增加變化:將 .feature
放大
接下來,我們可以視覺效果更吸引人一點:把 .feature
圖片放大。目前每個網格項目都只佔 1x1 的格子,也就是一欄一列。現在我們要讓 .featured
的項目放大到佔兩欄、兩列,也就是 2x2 的區塊。用 .featured
來鎖定這些項目,然後讓它們在水平和垂直方向上各跨兩個軌道,就能達成放大的效果。
不過這樣做會遇到一個小問題:當你讓某些項目變大時,根據項目的順序,有可能會在網格中產生空隙 (如右上角)。因為有些項目原本預期只佔一格,突然變大後可能會擠到別的項目,結果就出現一些空白。
在這個圖裡,因為網格裡的第 3 個項目放大了,要佔兩格寬和高,所以沒辦法塞進右上角的空間裡,結果它就被擠到下一行。為什麼會這樣?因為當你沒特別指定每個項目的位置時,瀏覽器會使用內建的「網格項目自動放置演算法 (Grid item placement algorithm)」來自動幫你排列。
這個演算法的預設行為是:按照 HTML 裡項目的順序,從左到右、從上往下放。但如果某個項目太大,放不下當前這一行,它就會被移到下一行,去找能放得下的空間。所以在這個例子中,第三個項目就被移到下方,因為那邊的位置才夠。
不過好消息是:CSS Grid 有提供一個屬性叫做 grid-auto-flow
,可以用來控制這個排版邏輯。預設值是 row
,就是我們剛剛說的「先按列再換行」;如果你設定成 column
,那它就會變成「先按行再換列」;更進一步,你可以加上 dense
,像這樣:
1 | grid-auto-flow: row dense; |
這樣做的效果是:演算法會盡量把小的項目「補進」那些因為大項目被跳過的位置。就算會讓項目呈現的順序和原本 HTML 的順序不完全一樣,它也會盡可能把版面填滿,不留空白。
現在,我們來更新一下樣式表:
1 | .portfolio { |
這段樣式使用了 grid-auto-flow: dense
,其實等同於寫成 grid-auto-flow: row dense
,因為 row
是預設值,就算省略也一樣有效。接著,鎖定 .featured
項目讓它在水平方向和垂直方向上各跨兩格,也就是:
注意這個範例只用 span
關鍵字來設定大小,並沒有明確指定項目要放在哪一格。這樣的寫法可以讓瀏覽器的「網格項目自動放置演算法自行決定這些項目要排在哪裡,不用特別指定位置,也避免出現錯位或衝突。
根據你螢幕寬度,你看到的畫面可能會跟上圖完全不一樣。這是因為我們用了 auto-fill
來決定垂直網格軌道的數量。如果你的螢幕比較寬,就能容納更多欄;如果螢幕比較窄,就只會顯示比較少的欄位。
我自己是用大約 1000px 左右的視窗來截圖,所以畫面上剛好出現了 4 欄。如果調整瀏覽器的寬度,網格就會自動適應空間變化,這就是 Grid 的厲害之處!
當你使用
grid-auto-flow: dense
時,畫面上項目的排列順序,可能跟 HTML 原始碼的順序不一樣。對大多數使用者沒差,但對鍵盤 (像是按 Tab 鍵) 或用螢幕閱讀器的使用者來說可能會有點混亂,因為這些輔助工具是根據原始碼的順序來瀏覽,不是照畫面上看到的順序。所以如果你很在意無障礙體驗 (accessibility) ,在使用dense
這個選項時要特別小心,確保不會造成使用上的混淆。
微調
現在我們已經完成一個複雜的網格佈局,而且還不需要指定每個項目位置,瀏覽器自動幫你排好,非常省力。不過最後還有一個小問題要解決:你會發現有些圖片放大 (.featured
項目) 並沒有完全填滿它所在的網格格子,導致下面或右邊多出一點小空隙。理想情況下,同一個網格軌道裡的所有項目,上緣和下緣都應該要對齊,這樣版面才整齊。目前我們的圖片上緣對齊沒問題,但下緣就有點歪。
我們來把空隙修正一下,還記得每個網格項目其實是一個 <figure>
元素,裡面有一張圖片:
1 | <figure class="featured"> |
預設情況下,每個 <figure>
(也就是網格項目) 會自動撐滿它被分配到的整個格子,但網格項目裡的子元素 (像圖片) 並不會自動撐滿整個高度。結果就是:<figure>
佔滿了格子,但 <img>
還是保留原來的大小,所以下面就出現一段空白。
要修正這種情況,我們可以用 Flexbox 來解決圖片下方出現空隙的問題。我們可以把每個 <figure>
設成一個彈性容器,接著,對圖片設定 flex-grow
,這樣它就會撐滿剩下的空間,不會是原本圖片的高度,下面也就不會有空白了。
但這樣會遇到另一個問題:圖片被強行拉伸的時候,寬高比例會跑掉,導致變形。CSS 有一個專門處理圖片拉伸的屬性叫做 object-fit
。預設情況下,<img>
的 object-fit
是 fill
,也就是整張圖片會撐滿 <img>
這個框框,不管比例對不對。如果你想讓圖片保持原來的比例,那可以改用這兩個設定:
cover
:讓圖片放大到填滿整個容器,即使有一部分被裁掉也沒關係。contain
:讓整張圖片完整顯示在容器裡,但容器裡可能會留一些空白。
下圖展示這些效果的差別,選擇哪一種取決於你希望圖片被裁切掉,還是要完整顯示。
這裡有一個很重要的觀念要釐清:<img>
有兩個層次的尺寸要分開看:
- 一個是外框大小,也就是
<img>
元素本身的width
和height
。 - 另一個是圖片內容大小,透過
object-fit
設定圖片在框裡面怎麼呈現。
預設情況下,這兩者大小是相同的。但透過 object-fit
可以控制圖片在框裡的呈現方式,而不會因此改變框的大小。
我們剛才用 flex-grow
讓圖片伸展去填滿整個網格格子,然後再加上 object-fit: cover
,讓圖片避免拉扯變形。cover
會讓圖片保持比例,並撐滿整個外框,雖然會裁掉圖片的邊角,但這是我們在這裡所做的取捨。
1 | .portfolio > figure { |
Subgrid
前面範例中,我們的網格結構都只侷限在 DOM 的兩個層級內:一個網格容器 (parent) 加上它的直接網格項目子元素 (children) 。不過在某些設計中,你可能會遇到更複雜的需求,像是:你需要對齊不在同一個父層底下的元素;或者你希望兩個元素,雖然不在同一層,卻能有一樣的大小或對齊方式。舉個例子:你可能想要讓兩個相同祖先的內容區塊,都能垂直對齊或寬度一致,無論內容多少。
在這個設計裡,每張卡片本身就是網格的一部分,所以它們會整齊地排成一列,並且高度一致。但不只這樣,卡片裡面的內容也會彼此對齊。像有些卡片的標題比較短,這時就會在上方加一些空間,讓它跟標題比較長的卡片對齊;每段內文的開頭也都對齊在同一高度,最底下的「Read More」連結也都整齊排在同一條水平線上。要做到內層與外層內容同步對齊的效果,最理想的方法是用新的 CSS 功能:子網格 (subgrid) 。
子網格的好處是:可以在一個網格容器裡再建立一個內層網格,並且讓這個內層網格的項目直接對齊外層網格的線條。這樣就可以讓整體看起來更一致。
在這個頁面中,每張卡片會跨三行,而卡片裡的標題、段落、按鈕也剛好對應到那三行的位置。不管是卡片本身還是卡片裡的內容,都是依照同一組網格線排列。
子網格 Subgrid 是一個比較新的 CSS 功能,已經支援大多數瀏覽器。你可以到這個網站查看最新的支援狀況:https://caniuse.com/css-subgrid
我們先建立一個最基本的卡片和樣式:
1 | <div class="author-bios"> |
1 | body { |
現在樣式加上去後,已經接近你想要的設計。不過,還差最後一步:我們來用子網格,讓卡片裡的內容也能跟整個網格對齊一致。
要在頁面上套用子網格,需要做兩件事:先在每個網格項目 (也就是卡片) 上加上 display: grid
,這樣就能在每張卡片裡建立一個內層網格;然後,在內層網格的 grid-template-rows
或 grid-template-columns
上設定 subgrid
,這表示:這個內層網格要使用父層網格的網格來對齊。
這樣一來,卡片裡的內容 (標題、文字、按鈕) 就會直接對齊其他卡片內容,達到完全一致的視覺效果。
1 | .card { |
因為子網格是比較新的功能,有些瀏覽器可能還不支援。如果你想要更保險一點,也可以用 CSS 的功能查詢 (feature query) 來判斷瀏覽器是否支援 subgrid
,像這樣寫:
1 | @supports (grid-template-rows: subgrid) { |
另外也可以用 grid-template-columns: subgrid
來對齊列 (columns),但前提是父層有定義網格。你也可以列和欄一起使用 subgrid
,讓內部區塊完整對齊父容器。
更進一步,你還可以把這個結構一層一層往下套,也就是在一個子網格裡面再建立另一個子網格,讓整個 DOM 結構中的區塊都能保持一致的網格對齊,非常適合用在大型或模組化的設計裡。
在 subgrid
中,有一個很棒的特性是:你可以繼承父網格的所有線號 (line numbers) 、線名稱 (line names) 和區域名稱 (grid area names) ,這代表你可以直接用這些資訊來擺放子網格中的元素。舉個例子,假設你想要把卡片中的標題全部放在第二行,你可以這樣寫:
1 | .card > h3 { |
除了可以繼承父網格的線名稱,你也可以在 subgrid
裡定義自己的線名稱,即使父網格沒有那些名稱:
1 | .card { |
這樣就能使用自訂的線名稱來放置元素。grid-row: line3 / line4;
這表示你要把項目放在第 3 條和第 4 條橫向網格線之間的位置。
Masonry
補充小知識,未來 CSS 可能會加入新功能叫做「masonry layout (瀑布流排版) 」,這種排版方式在圖片集 (像 Pinterest) 裡很常見,但目前要靠 JavaScript 才能實現。
Masonry 的特色是:它會把項目放在等寬的欄位中,但每個項目的高度可以不一樣,排版就不會要求每一列都整齊對齊,能更靈活地呈現不同大小的內容。
你可以到這個網站查看最新的支援狀況 https://caniuse.com/?search=masonry
對齊屬性
在 CSS Grid 模組中,我們會用到一些跟 Flexbox 很像的對齊屬性,另外還多了幾個新的。我們其實已經在 Flexbox 介紹過大部分,但有一些屬性是只有在 Grid 裡才有作用的。當你需要更細緻地控制網格排版時,這些對齊屬性就會派上用場。
首先是三個 justify
開頭的屬性,這些是用來 控制水平方向 (橫向) 的排列。接著是三個 align
開頭的屬性,用來控制垂直方向 (直向) 的排列。
最後補充一點:CSS 還提供了簡寫屬性,可以同時設定 align-*
和 justify-*
的值:
place-content
:同時設定align-content
和justify-content
place-items
:同時設定align-items
和justify-items
place-self
:同時設定align-self
和justify-self