耽擱了好久一直沒寫 Grid 布局,主要是寫布局的文章太累人??。這期就朝花夕拾,寫寫 Grid layout 的入門教程。
Grid Basic
Grid layout 翻譯過來叫網(wǎng)格布局,我先介紹一下 Grid 里的幾個基本術(shù)語:
Grid Container
網(wǎng)格容器就是被設(shè)置為display: grid的元素,通俗來說就是所有網(wǎng)格的最外層:
.grid-container {
display: grid;
}
Grid Items
網(wǎng)格項就是網(wǎng)格容器里的每一個子元素。如下所示,header、aside、main和footer就是grid-container的網(wǎng)格項。(p.s. 孫子元素不會受到祖先網(wǎng)格屬性的影響)
<div class="grid-container">
<header></header>
<aside></aside>
<main>
<!-- doesn’t effected!!! -->
<div class="grandson"></div>
<!-- doesn’t effected!!! -->
</main>
<footer></footer>
</div>

Grid Columns
既然是網(wǎng)格,自然有列和行的概念,我們先說“列”。網(wǎng)格容器添加grid-template-columns屬性便可激活列屬性:下方示例中,我們把網(wǎng)格項排成了兩列。實現(xiàn)上只需把兩列的長度(200px、auto)從左至右枚舉出來即可:
.grid-container {
display: grid;
grid-template-columns: 200px auto;
}

除了利用單位或 auto 來指定每一列的長度,我們也可以讓列按一定比例排布。下方示例中,實現(xiàn)了左右兩列 1:2 比例的布局——只需在grid-template-columns指定1fr和2fr即可。
.grid-container {
display: grid;
grid-template-columns: 1fr 2fr;
}

p.s. fr 是 franction 的縮寫
Grid Rows
同理,grid-template-rows會激活行屬性,也就是用來定義每一行的高度,屬性同樣可以是基本單位(px,rem,%),也可以是fr比例:
.grid-container {
display: grid;
grid-template-columns: 1fr 2fr;
grid-template-rows: 100px 200px;
}

Grid Gaps
若想為這些排布的 items 添加間距(槽),可以使用grid-gap屬性。單獨定制橫軸或縱軸的間距,還有grid-column-gap和grid-row-gap這兩個屬性。
.grid-container {
display: grid;
grid-template-columns: 1fr 2fr;
grid-template-rows: 100px 200px;
grid-column-gap: 10px;
grid-row-gap: 10px;
/* or in simplified form */
grid-gap: 10px;
}

Grid Cells & Gird Lines
在grid-template-columns和grid-template-rows排布后,橫軸和縱軸交匯,就形成如下所示的單元格(grid cell)和標(biāo)注為 1、2、3... 的網(wǎng)格線(grid line)。這里我提一下,cell 和 item 是不一樣的概念:cell 是網(wǎng)格的基本單元,而 item 是可以橫跨多個 cell 的 DOM 元素。

Grid Layout
上文我們介紹了幾個網(wǎng)格布局的基本術(shù)語;若是到此為止,那也頂多是加強版的 table 布局罷了。而網(wǎng)格布局的真正特別之處是在 grid cell 基礎(chǔ)上的網(wǎng)格定位。
我們還是從傳統(tǒng)布局的弊端說起,下圖是一種很常見的頁面布局。

這種布局實現(xiàn)上很直白:先把整體分成三行,再把中間那行分成兩列。HTML 便簽大體如下所示:
<body>
<header class="row"></header>
<div class="row">
<aside class="column-aside"></aside>
<main class="column-main"></main>
</div>
<footer class="row"></footer>
</body>
實現(xiàn)很簡單,就不寫 CSS 了;只是在語義標(biāo)簽層面上,header、footer 和 aside、main 理應(yīng)是同級;但為了布局方便不得不把 aside 和 main 做成了孫子節(jié)點,有點怪怪的。不過整體也沒太大問題。如果再成換更復(fù)雜的布局呢,比如,回字形?

用傳統(tǒng)布局技術(shù)(如 flex)實現(xiàn)回字形,代碼量會立馬暴漲。更糟糕的是,這種排版將犧牲掉所有語義標(biāo)簽——你的關(guān)注點只會在各種層層嵌套的 div 上了。
Grid Position
網(wǎng)格布局能幫到什么呢?我前文也提到過,Grid items 可以橫跨多個 Grid cells,就從這里入手。
我們再看看上面那個回字形布局,本質(zhì)上不就是個九宮格嘛?我們用 repeat 方法給網(wǎng)格容器寫一個 3×3 的 cell 九宮格:
.grid-container {
display: grid;
grid-template-columns: repeat(3, 100px);
grid-template-rows: repeat(3, 100px);
}
最后只要把網(wǎng)格項 header 覆蓋在第 1 和第 2 個 cell 上、aside 在 4 和 7 上、main 在 3 和 6、footer 在 8 和 9 上,即可完成布局。

那網(wǎng)格布局怎么為網(wǎng)格項定位這些 cell 呢?用坐標(biāo)呀!從 (grid-column-start, grid-row-start) 到 (grid-column-end, grid-row-end) 所在的矩形區(qū)域來定位網(wǎng)格項的排布。橫坐標(biāo)值就是 Y 軸(Column Grid Line)所顯示的數(shù)值,縱坐標(biāo)就是 X 軸(Row Grid Line)所顯示的數(shù)值,起始數(shù)值都是 1。

上圖所示,header 位于 (1,1) 到 (3,2) 這塊矩形區(qū)域內(nèi),我們?yōu)?header 添加如下 CSS 屬性;header 便會自動定位到左上角那塊粉紅色區(qū)域了。
header {
grid-column-start: 1;
grid-row-start: 1;
grid-column-end: 3;
grid-row-end: 2;
}
其他幾塊區(qū)域的布局我就不寫出來了,大家有興趣的話自己算一下坐標(biāo)即可。
Grid Areas
除上面這種二維坐標(biāo)定位的方式,CSS 網(wǎng)格布局還提供了一種更無腦的定位方式——grid-template-areas——可以為網(wǎng)格設(shè)置區(qū)域模版,比如上面的回字形布局模版就如下所示:
.grid-container {
display: grid;
grid-template-areas:
"h h m"
"a . m"
"a f f";
}
這個模版我寫得很簡單,解釋一下:就是由九個字符及空格所示意的九宮格模版。以左上角 h h 為例,兩個相鄰的 h 組成了一個所謂的“命名網(wǎng)格區(qū)”,表示網(wǎng)格區(qū)名為 h 的網(wǎng)格項將會被排布在第 1、2 兩個 cell 上。a、m、f 分別是另外三個網(wǎng)格項的標(biāo)識符;中間的 . 是一個空白區(qū)域的占位符,我本人習(xí)慣用這個字符,大家盡可以挑選自己喜歡的占位符。
之后,我們再為 header、aside、main、footer 分別設(shè)置映射區(qū)域——grid-area,這四個網(wǎng)格項就自動排布到各自模版位上去了。
header { grid-area: h; }
aside { grid-area: a; }
main { grid-area: m; }
footer { grid-area: f; }
再回頭看一下網(wǎng)格布局的 HTML,語義標(biāo)簽不需要為布局做任何改變。這是較傳統(tǒng)布局的重大改進(jìn),HTML 結(jié)構(gòu)和 UI 終于實現(xiàn)了分離。
<body class="grid-container">
<header></header>
<aside></aside>
<main></main>
<footer></footer>
</body>
Grid Kiss
grid-template-areas 還需要為各個網(wǎng)格項命名模版區(qū)域,模版能不能自己幫我們映射到相應(yīng)的 html tag 或是 class 上去呢?這樣寫起來似乎更省事。
嗯,還真有人想到了這一點——一個叫 postcss-grid-kiss 的 postcss 插件。Grid-kiss 為我們實現(xiàn)了一種很有趣的 CSS 布局方案:讓所有的布局變成一幅“簡筆畫”:
.grid-container {
grid-kiss:
"+-------------+ +-----+"
"| header | | |"
"+-------------+ | |"
"+-----+ |main |"
"| | | |"
"|aside| +-----+"
"| | +--------------+"
"| | | footer |"
"+-----+ +--------------+"
;
}
它的實現(xiàn)就是把這副文本流圖轉(zhuǎn)化成 grid 語法樹。有一句廣告說的好,“Grid-kiss,布局從未如此簡單”
IE support
最后再說一下 IE,早在 E10 的時候它就已經(jīng)支持網(wǎng)格布局了——還是挺超前的。只可惜 IE 的 Grid 語法別樹一幟,未被大眾認(rèn)可;方法與現(xiàn)代 Grid 一比,更相形見絀了。那 IE 上要怎么使用網(wǎng)格布局呢?我們還是得依靠 PostCSS——CSS 里的 Babel,它有個叫 autoprefixer 的插件可以幫我們完成 Modern Grid 到 IE Grid 的轉(zhuǎn)換;這里(Autoprefixer online)還有一個在線的轉(zhuǎn)換網(wǎng)站,有興趣的朋友可以試一下。
若你已經(jīng)使用現(xiàn)代 JS 框架(如 Vue、React),它們的腳手架大多內(nèi)置了 postcss 和 autoprefixer,基本上就是無配置使用。上面的 grid-kiss,也只要給 postcss 配置加個插件,親測 IE11 可用。至于 IE 的 Grid 語法,就讓它隨風(fēng)消逝在歷史的長河之中吧。
小結(jié)
上次看了篇文章,提到 CSS Grid 已是一種很大眾化的前端技術(shù)。但我身邊的 team,只能說很少很少很少有人使用。我猜原因有多種:
- 熟練掌握 CSS 的開發(fā)人員本身就很少,大家形成了一種“默契”——不要增加認(rèn)知復(fù)雜度
- 技術(shù)革新太快,一開始想著“讓子彈飛一會兒”,但是之后就再也沒人提起了
- 傳統(tǒng)認(rèn)知中后端重于前端,很多團(tuán)隊也無心推動前端升級
當(dāng)然,這些種種我們也應(yīng)理解,畢竟這只是一種布局技巧,在一個產(chǎn)品的范疇中占不了太多分量;甚至于技術(shù)本身,只要別太爛,也并不是決定產(chǎn)品成敗的關(guān)鍵。所以嘛,對于新技術(shù)也,能用則用,不能用也不要太過執(zhí)著;做人嘛,最重要的是開心。
相關(guān)
文章同步發(fā)布于an-Onion 的 Github。碼字不易,歡迎點贊。