新手在使用網格佈局時常見的一個問題是:如何對一個不包含任何內容的單元格新增樣式。在當前的Level 1規範中還無法做到,因為無法選定空單元格或網格區域並對其新增樣式。也就是說,想要設定樣式必須插入一個元素。
本文我將會介紹如何使用CSS生成內容為空單元格新增樣式,而不新增多餘的空元素,同時會給出一些示例。
為什麼不能對空區域設定樣式?
網格規範的開頭有提到:
“本 CSS 模組定義了一個用於優化UI設計的基於網格的二維佈局系統。在網格佈局模型中,網格容器的子節點可以定位到預定義的彈性或固定大小的網格中的任意位置。“
注意關鍵詞”網格容器的子節點“。該規範定義了在父元素上建立網格的哪些子節點能夠被定位。其沒有定義任何關於網格的樣式,也沒有像多列布局的 column-rule
那樣的屬性。我們是對子專案新增樣式,而不是網格本身,即需要有某個元素來應用樣式。
使用多餘元素作為樣式鉤子
新增樣式的方法之一是在文件中新增多餘的元素,如 span 或 div。一般來說程式設計師都不喜歡這個方法,雖然多年來一直是通過多餘的“行包裹元素”,然後通過浮動實現網格佈局。而明顯的空元素比某些隱式的多餘包裹元素更讓人覺得不爽。
如下例所示,將空元素變成網格項,並對其新增背景和邊框,就像包含內容的元素一樣。
檢視 Rachel Andrew (@rachelandrew) 的 Empty elements become Grid Items 。
Eric Meyer 在他的個人網站 A List Apart 的文章 Faux Grid Tracks 中提倡使用 b
元素作為冗餘元素,因為它沒有任何語義,在所有的標籤中作為一個鉤子很短而且很明顯。
插入額外的 div
或 b
元素不算是什麼大問題,所以若有需要,我不會覺得有什麼不好的。而在 Web 開發中,通常一開始使用無優化的方法完成任務,直到有更好的解決方案。我一般儘可能的將樣式放在統一的地方,這樣的樣式更易於重用,且不需要擔心額外的標籤。正是出於這個原因,我傾向用生成內容的方法,在我的 formatting books with CSS 一文中,生成內容的使用頻率非常高。
使用生成內容作為樣式鉤子
CSS生成內容使用 ::before
和 ::after
CSS 偽類以及 content
屬性在文件中插入內容。插入內容可能會用於插入文字,雖然這是可能的,但此處我們的目標是插入空元素作為網格容器的直接子元素。插入一個可以新增樣式的元素。
在下面的例子中,有一個包裹元素作為網格容器,在其中巢狀了另一個元素。這個單獨子元素作為網格項。我在容器上定義了三列三行的網格,然後使用網格線對其定位,將其它定位於中間的網格單元格中。
1 2 3 |
<div> <div></div> </div> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
.grid { display: grid; grid-template-columns: 100px 100px 100px; grid-template-rows: 100px 100px 100px; grid-gap: 10px; } .grid > * { border: 2px solid rgb(137,153,175); } .item { grid-column: 2; grid-row: 2; } |
使用 Firefox Grid Inspector 檢視這個示例,其上覆蓋著網格線,雖然這樣能看到其他空單元格的位置,但若想要對其新增背景或邊框,則需要新增的子元素,通過生成內容可以實現。
單個網格項,通過 Firefox Grid Inspector 突出顯示
在CSS中,在網格容器的偽元素 ::before
和 ::after
中新增一個空字串。他們將作為網格項並填充在容器中。然後對其設定樣式,新增背景色,如同普通的網格項一樣對其定位。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
.grid::before { content: ""; background-color: rgb(214,232,182); grid-column: 3; grid-row: 1; } .grid::after { content: ""; background-color: rgb(214,232,182); grid-column: 1; grid-row: 3; } |
單個網格項,兩個生成內容作為網格項
在文件中,我們只有一個直接子元素,多餘的其他樣式元素則在CSS中,這看起來挺合理的,因為這些元素的目的就是用於新增樣式。
生成內容方法的侷限
若你想要對右上或左下的網格項設定樣式那麼就有一個問題了,因為生成內容是有限的,多次重複設定 ::before
和 ::after
偽元素是無效的。例如,無法通過偽元素來生成 CSS 網格棋盤。若你確實需要對很多空單元格新增樣式,那麼在將來,你也許能通過”Filler B”的方式實現。
生成內容方法也可能會讓專案以後的開發人員感到困惑。由於選擇器目標是容器,如果在其他地方重複使用該類,將會出現生成內容,如果這正是想要的效果,自然是好的。在下面的例子中,在標題的任一側新增一個裝飾橫線,每個 h1
都有這些橫線是合理的。但是,如果不知道有這個偽類,則裝飾橫線會令人感到困惑!在CSS中新增註釋將對此有所幫助。我一般會將他們作為一個元件庫使用,在統一的地方定義元件,清晰的說明當類被應用到元素時會發生什麼。
花式標題
我最喜歡的運用生成內容的技巧之一是對標題設定樣式。過去,對標題設定樣式需要通過額外的包裹元素和絕對定位來實現。但當頁面內容來自CMS時,通常是無法新增額外的包裹元素的。
而使用網格和生成內容,我們可以在標題的任意一側新增橫線,而無需任何附加標籤。該線將根據可用空間增長和縮小,並且當網格在瀏覽器中不可用時,將優雅地回退到普通的居中效果。
我們需要實現的標題樣式
標籤為一個簡單的 h1
。
1 |
<h1>My heading</h1> |
在 h1
的樣式中,我建立了一個三列網格。grid-template-columns
的值表示一列為 1fr
,一列為 auto
,最後一列為 1fr
。兩個 1fr
的列的寬度將會自適應,在標題佔據 auto
大小的空間之後,平分剩餘可用空間。
設定 text-align
屬性為 center
以便標題在不支援網格的瀏覽器中水平居中。
1 2 3 4 5 6 |
h1 { text-align: center; display: grid; grid-template-columns: 1fr auto 1fr; grid-gap: 20px; } |
現在新增生成內容,在標題文字前後各新增一行。同時將其對應樣式規則包含在特性查詢(Feature Query)中,因此不會在不支援網格佈局的瀏覽器中出錯。
橫線其實是生產內容的邊框。
1 2 3 4 5 6 7 8 |
@supports (display: grid) { h1:before, h1:after { content: ""; align-self: center; border-top: 1px solid #999; } } |
也就是說,你能使用類似的方式新增任何樣式,甚至在元素周圍新增圖示。需要注意,將生產內容放入一個單獨的列中時則無法做到生成內容與標題的重疊。即使是絕對定位也無法解決該問題。所以,你需要確實你想要實現的效果是否可通過此方法實現。
參考 Rachel Andrew (@rachelandrew) 的 Generated Content heading example 。
這是通過網格佈局實現漸進增強的一個很好的例子,即使你不準備將網格作為主要佈局方式,也可以使用。回退時的標題簡單明瞭,而支援的瀏覽器也可以有更好效果,無論如何都可以正常檢視內容。Eric Meyer 也採用了類似的方法,使用生成的內容為 blockquote 元素新增方便定位的引號。
我常常不會在一開始就確定要使用網格佈局。一般是在具體實現時才開始選方案。基於此,推薦大家不要將網格佈局僅看作頁面元件佈局的方法,因為有可能會在實現細節時忘記它。
新增背景和邊框
我們也可以使用生成內容來堆疊網格項; 其實,一個網格項可以佔據不止一個單元格。包括通過生成內容插入的專案。
在下一個例子中,我設計了兩段內容區域和一個全寬網格項。內容區域有一個背景,在全寬網格項也有相同的背景。
我們的目標佈局
程式碼中有一個包裹元素,內容區域和全寬網格項為其子元素,通過基於行的定位來設定網格項的位置。
1 2 3 4 5 6 7 8 9 10 11 |
<article> <section> <p>…</p> </section> <div> <img src alt="“Placeholder”"> </div> <section> <p>…</p> </section> </article> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
.grid { display: grid; grid-template-columns: 1fr 20px 4fr 20px 1fr; grid-template-rows: auto 300px auto; grid-row-gap: 1em; } .section1 { grid-column: 3; grid-row: 1; } .section2 { grid-column: 3; grid-row: 3; } .full-width { grid-column: 1 / -1; grid-row: 2; background-color: rgba(214,232,182,.5); padding: 20px 0; } |
這樣就能將佈局設定為目標那樣了,但對網格項設定背景時發現背景不會出現在 section
和全寬圖片之間的 row-gap
區域。
1 2 3 4 |
.section { background-color: rgba(214,232,182,.3); border: 5px solid rgb(214,232,182); } |
背景僅出現在內容區之下
若將 grid-row-gap
屬性移除,而用 padding 模擬也無法實現將背景置於全寬項之下。
此時就是使用生成內容的最佳場景了,通過 ::before
在網格包裹元素之前新增一個生成內容,同時對其設定一個背景色,若什麼都不做,那麼此生產內容預設放置在網格的第一個單元格中。
1 2 3 4 5 |
.grid::before { content: ""; background-color: rgba(214,232,182,.3); border: 5px solid rgb(214,232,182); } |
生成內容佔了網格的第一個空單元格
然後,使用基於行的定位來定位生成內容,將其擴充套件到所有應顯示背景色的區域。
1 2 3 4 5 6 7 |
.grid::before { content: ""; background-color: rgba(214,232,182,.3); border: 5px solid rgb(214,232,182); grid-column: 2 / 5; grid-row: 1 / 4; } |
完整演示在CodePen上。
參考 Rachel Andrew (@rachelandrew) 的 Generated Content background example 。
通過 z-index
控制層疊
在上面的例子中,生成內容是通過插入 ::before
實現的。這意味著其他元素在它之後,它位於層疊的底部,因此將顯示在想要的其餘內容的後面。可以通過 z-index
來控制層疊。也可以將 ::before
選擇器更改為 ::after
。生成內容背景則位於所有內容的頂部,如同從邊框在影象上那樣。這是因為它現在已經成為網格容器中的最後一項,它是最後被繪製的,因此出現在“頂部”。
要改變這一點,需要給這個元素一個比所有元素更低的 z-index
值。若沒有設定過 z-index
值,則最簡單的做法是給生成內容 z-index
設為 -1
。這會導致它成為層疊中的第一項,因為它的 z-index
最低。
1 2 3 4 5 6 7 8 |
.grid::after { z-index: -1; content: ""; background-color: rgba(214,232,182,.3); border: 5px solid rgb(214,232,182); grid-column: 2 / 5; grid-row: 1 / 4; } |
以這種方式新增的背景不僅僅能將背景放在所有內容的下方。若只在部分元素後突出背景則可以產生一些有趣的效果。
規範未來會解決這個問題麼?
CSS 網格規範確實沒有說明如何為單元格新增背景和邊框。工作組與社群的討論可參考:GitHub上討論分支
如果你的用例場景不好用生成內容解決,那麼可以將你的想法新增到該分支。你的意見和用例有助於呈現開發者對該特性的關注點,同時請確定你的意見和用例的準確度。
請幫忙新增更多示例!
如果本文讓你嘗試使用了生成內容,或者如果你已有示例,請將其新增到評論中。在生產環境中用到的網格佈局的都是全新的示例,所以,當我們將網格與其他佈局方法結合在一起使用時,會有很多“意外之喜”。