深刻淺出grid佈局

原文發佈於 github.com/ta7sudan/no…, 如需轉載請保留原做者 @ta7sudan.css

注意, 由於 grid 標準還在修訂中, 如下內容可能會隨着標準的改變而過期(好比聽說以後的標準可能會將 grid-row-gapgrid-column-gap 改成 row-gapcolumn-gap, 以便與 column 佈局統一), 固然我也會盡可能及時修正. 如下內容寫於 2018/2/27.html

本文中提到的 content-box 均是在未改變 box-sizing 的狀況下適用.css3

關於 grid 的應用場景, 或者說比其餘佈局有什麼優點? 在有既要按水平方向進行對齊, 同時又要保證垂直方向的對齊時, 可使用 grid 佈局.git

關於 grid 的建議是, 若是面向的用戶是最新的瀏覽器, 那基本能夠用於生產環境, 不然不建議用於生產環境.github

在介紹 gird 佈局以前咱們先來看幾幅圖算法

img101

整個最大的整塊矩形塊咱們稱做網格容器(grid container), 也是咱們的佈局容器, 即容器裏面的元素都按照網格進行佈局. 圖中的虛線咱們稱爲網格線(grid line), 網格線是一個佈局時的參考線, 並不會被實際渲染出來. 網格線帶有一個屬性, 那就是編號, 即每一個網格線都是有本身的序號的.瀏覽器

img102

像這樣, 不管水平方向仍是垂直方向的網格線都有本身的編號. 編號是網格線固有的屬性, 無需開發者本身定義, 默認從 1 開始, 從上往下依次遞增, 從左往右依次遞增(但也不必定, 還與書寫模式有關). 網格線能夠有本身的名字, 名字由開發者定義, 具體參考後文.bash

網格容器被網格線分隔成了 3x3 個單元格, 每一個單元格咱們稱爲網格單元(grid item). 網格線也把網格容器分紅了三行三列, 這裏行和列咱們都稱爲網格軌道(grid track). 其中深色的矩形塊 One, Two, Three, Four 咱們稱爲網格項目(grid item).ide

那網格單元和網格項目有什麼區別? 答案是網格單元不是一個元素, 只是元素定位的參考系, 是一個邏輯概念, 不會被渲染到頁面. 網格項目是咱們實際渲染的元素, 網格項目根據網格單元進行定位, 網格項目能夠佔據一個網格單元, 也能夠佔據多個網格單元, 像下面這樣.wordpress

img104

能夠看到, 這裏的 One, Two 等矩形塊都是網格項目, 這纔是實際渲染出來的樣子, 它們根據網格單元進行定位, 有些佔據一個網格單元, 有些佔據多個網格單元, 多個網格項目也可能重疊佔據同一個網格單元.

img103

可是網格項目不只僅能夠根據網格單元進行定位, 還能夠根據網格區域進行定位. 咱們能夠從一片連續的網格單元中取出一個矩形, 如紅框所示. 咱們能夠把這四個網格單元定義成一個網格區域(grid area), 而後讓一個網格項目佔據一塊網格區域(目前只支持矩形的網格區域, 還不支持 L 型的網格區域).

有人可能已經注意到, 第三幅圖中的網格項目之間有些空白間隔, 咱們把這些間隔叫作網格間距(gutter). 那網格間距是否佔用網格軌道的寬度呢? 下圖能夠更清楚的代表這點.

img105

能夠看到, 藍色的是網格單元, 網格單元所處的地方是網格軌道, 而網格間距並無佔用網格軌道的空間.

OK, 經過以上幾幅圖, 咱們大概能夠知道, 網格容器, 網格軌道, 網格線, 網格單元, 網格區域, 網格間距都是邏輯概念, 是網格容器內部元素定位的參考系, 它們(除了網格容器和網格間距)不會被實際渲染出來, 而網格項目則是網格容器內部實際存在的元素, 會被渲染出來.

整個 grid 佈局的過程就像是在一個事先定義的網格里面鋪地磚, 咱們把網格項目鋪在網格單元上, 一個網格項目能夠佔據一個或多個網格單元(爲了便於理解, 這裏咱們先用佔據這個詞, 但準確說並非網格項目佔據網格單元, 而是指定網格項目的定位區域一個或多個網格單元. 以後的內容中會更詳細地解釋這點), 網格項目之間也能夠重疊. 因此使用 grid 佈局的基本套路就是: 先定義網格容器, 再定義網格單元, 而後指定網格項目怎麼個"鋪"法.

須要注意的是, 咱們說的定位是指網格項目的 margin-box 擺放在網格單元/網格區域之中, 網格單元/網格區域就像一個 BFC 那樣, 使得網格項目的外邊距不和其餘網格項目的外邊距發生摺疊.

這個圖中清晰地代表了一個網格項目的 margin-box 位於一個網格區域(三個網格單元)之中.

幾個術語

接着咱們來了解一些術語, 其中有些前面咱們已經提到過, 不過這裏給出一些更具體的定義.

  • 網格容器(grid container), display: grid;display: inline-grid; 的元素, 整個網格系統都在網格容器的 content-box 中, 而網格容器對外(其餘元素)依然是塊級元素或者行內元素. 這個元素的全部正常流中的直系子元素(包括匿名盒子)都將成爲網格項目(grid item) (W3C: Each in-flow child of a grid container becomes a grid item). display: grid; 會建立一個GFC(grid formatting context), 相似 BFC 的東西.
  • 網格線(grid line) 是網格容器的水平或垂直分界線. 網格線存在於列或行的任意一側. 它們能夠經過數字索引(從1開始)來引用或者經過開發者自定義的名字來引用. 做爲元素定位時的參考系, 是一個邏輯意義上的實體, 不會被渲染.
  • 網格軌道(grid track) 是任意兩條相鄰網格線之間的空間, 即一行或者一列, 以後簡稱軌道/行/列. 做爲元素定位時的參考系, 是一個邏輯意義上的實體, 不會被渲染.
  • 網格單元(grid cell) 是行和列相交的空間, 是 grid 佈局的最小單元. 做爲元素定位時的參考系, 是一個邏輯意義上的實體, 不會被渲染.
  • 網格區域(grid area) 是一塊邏輯意義上的空間, 它包含了一個或多個相鄰的網格單元, 由某四條網格線圍成. 網格區域能夠經過名字定義, 也能夠經過網格線定義. 做爲元素定位時的參考系, 是一個邏輯意義上的實體, 不會被渲染.
  • 顯式網格(explicit grid)和隱式網格(implicit grid), 經過 grid-template-columnsgrid-template-rows 建立出來的軌道, 咱們稱它們屬於顯示網格. 可是這些軌道不必定能容納全部的網格項目, 瀏覽器根據網格項目的數量計算出來須要更多的軌道, 就會自動生成新的軌道, 這些自動生成的軌道咱們稱它們屬於隱式網格.
  • 網格間距(gutter) 經過 grid-row-gapgrid-column-gap (可是如今 W3C 標準中已經修改成經過 row-gap, column-gap 來定義了. 沒錯, 就是那個列布局中也會用到的 column-gap)定義相鄰軌道之間的空白空間. 是一個額外的不佔用軌道空間的空白. 對於跨越多個網格單元的網格項目, 不只僅佔據單元格的空間, 也佔據其中的網格間距的空間. 網格間距只會在顯示網格中出現.
  • 剩餘空間是我本身發明的詞, 即網格容器某方向上的大小(content-box 的 width)減去該方向上全部的網格間距之和剩下的空間, 也即全部行/列的寬度之和.

接下來也會經過具體例子進一步說明這些名詞.

新單位 fr 和 repeat(), minmax() 函數

在看具體例子以前先了解下隨着 grid 佈局一同出現的新單位 fr 以及兩個新函數 repeat(), minmax(). 這裏只簡單地介紹這些內容, 以後會有更具體的說明.

fr

fr 其實就是分配剩餘空間的權重, 熟悉 flex-grow 的話能夠很容易 get 到這一點.

img107

能夠看到, 咱們有一個網格容器, 容器有網格間距, 其中有三個網格項目, 它們的寬度都是 1fr, 因此它們均分了剩餘空間.

img108

而 2fr 的話則是這樣, 它的工做機制就像 flex-grow 那樣.

repeat()

repeat() 函數能夠簡單地理解爲宏展開, 就像 Sass 中的 mixin 那樣, 舉個例子

grid-template-columns: repeat(3, 1fr);
grid-template-columns: repeat(3, 1fr 2fr);
複製代碼

至關於

grid-template-columns: 1fr 1fr 1fr;
grid-template-columns: 1fr 2fr 1fr 2fr 1fr 2fr;
複製代碼

repeat(3, 1fr) / repeat(3, 1fr 2fr) 展開成了 1fr 1fr 1fr / 1fr 2fr 1fr 2fr 1fr 2fr, . 固然, 這裏只是簡單地這麼理解, 以後還有更具體的說明.

minmax()

minmax() 用來指定一個軌道的寬度值, 好比你能夠指定一個軌道寬固定爲 200px, 你也能夠指定一個軌道寬度爲 minmax(200px, 1fr), 這樣的話, 軌道寬度是彈性的, 最小爲 200px, 最大爲 1fr.

使用 grid 佈局

使用 grid 佈局的通常套路能夠總結爲:

定義網格容器 -- 劃分網格單元 -- 根據網格單元定義網格項目

來看一個例子

<div class="main m0">
	<div class="item">1</div>
	<div class="item">2</div>
	<div class="item">3</div>
	<div class="item">4</div>
</div>
複製代碼
.main {
	width: 300px;
	background: #fff4e6;
	border: 2px solid #f76707;
	border-radius: 5px;
	margin-bottom: 50px;
	display: grid;
}

.item {
	color: #d9480f;
	background: #ffd8a8;
	border: 2px solid #ffa94d;
	border-radius: 5px;
}
.m0 {
	grid-template-columns: 1fr 1fr 1fr;
	grid-template-rows: 50px 100px;
}
複製代碼

img109

咱們先定義了一個網格容器, 而後 grid-template-columns: 1fr 1fr 1fr; 定義了三列, 每列寬爲 1fr, grid-template-rows: 50px 100px; 定義了兩行, 其中第一行爲 50px 高, 第二行爲 100px 高. 網格項目默認被放入一個網格單元中, 按照從左往右從上往下排列, 因而獲得如圖效果. 其中 grid-template-columns: 1fr 1fr 1fr; 也能夠寫成 grid-template-columns: repeat(3, 1fr);.

顯式網格和隱式網格

在上面的例子中, 咱們經過 grid-template-columnsgrid-template-rows 把網格容器劃分紅了 2x3 個網格單元, 假如咱們只指定一行的話又是怎樣?

.m1 {
	grid-template-columns: 1fr 1fr 1fr;
	grid-template-rows: 50px;
}
複製代碼

img110

能夠看到, 儘管咱們沒有指定兩行, 可是網格容器依然被劃分紅了 2x3 個網格單元, 其中第一行的全部網格單元是咱們經過 grid-template-columnsgrid-template-rows 手動建立的, 咱們把這種手動建立(即寬高都是由開發者定義的)的行和列產生的網格單元稱爲顯式網格(explicit grid), 第二行是瀏覽器根據網格項目的個數計算自動生成的, 咱們把這種由瀏覽器自動生成的網格單元爲隱式網格(implicit grid).

咱們指定了第一行爲 50px 高度, 可是第二行由於是自動生成的, 因此高度默認由瀏覽器根據內容決定. 那是否是咱們就沒辦法指定隱式網格的大小了? 也不是, 咱們能夠經過 grid-auto-columnsgrid-auto-rows 來指定隱式網格的大小.

.m2 {
	grid-template-columns: 1fr 1fr 1fr;
	grid-template-rows: 50px;
    grid-auto-rows: 100px;
}
複製代碼

img112

能夠看到, 第二行又變回了 100px 高.

既然能夠自動生成行, 那何時自動生成列? 咱們能夠經過 grid-auto-flow 來改變網格項目的排列方向.

.m3 {
	grid-template-columns: 1fr;
	grid-template-rows: repeat(3, 50px);
	grid-auto-flow: column;
}
複製代碼

img113

這裏咱們指定了三行, 可是隻指定了一列, 可是在 grid-auto-flow 的做用下, 本來先從左往右, 再從上往下排列的網格項目變成了先從上往下, 再從左往右排列, 因而第二列屬於隱式網格.

到這裏, 最基本的 grid 佈局方式就比較清楚了, 不過若是 grid 佈局僅僅是這些內容, 那前面我也就不用廢話這麼多了.

以上代碼見 demo.

基於網格線的定位

咱們以前說了, grid 佈局的通常套路是定義網格容器 -- 劃分網格單元 -- 根據網格單元定義網格項目, display: grid; 完成了定義網格容器這步. 經過 grid-template-columns grid-template-rows grid-auto-columns grid-auto-rows 定義了 mxn 個網格單元, 完成了劃分網格單元這步. 可是咱們並無根據網格單元定義網格項目, 網格項目是瀏覽器按照特定順序自動填充到每一個網格單元中的, 那要怎麼完成這一步呢?

最簡單的是經過網格線, 還記得以前提到過, 網格線是有本身的編號的, 也能夠有本身的名字, 接下來咱們經過編號和名字來完成這最後一步.

<div class="main m0">
	<div class="item">1</div>
	<div class="item">2</div>
	<div class="item">3</div>
	<div class="item">4</div>
</div>
複製代碼
.m0 {
	grid-template-columns: repeat(5, 1fr);
	grid-template-rows: repeat(4, 50px);
}
.m0 > .item:nth-child(2) {
	grid-row-start: 2;
	grid-row-end: 4;
	grid-column-start: 2;
	grid-column-end: 4;
}
複製代碼

img114

咱們定義了 4x5 個網格單元, 從調試工具能夠很清楚地看到, 第二個網格項目佔據了 2x2 個網格單元, 因此看起來比其餘幾個網格項目大了很多. 而這裏, 其實就是咱們經過網格線指定了第二個網格項目的範圍, grid-row-start 指定了網格項目的水平方向的起始邊是水平方向編號爲 2 的網格線, grid-row-end 指定了水平方向結束邊是水平方向編號爲 4 的網格線, grid-column-start 指定了垂直方向起始邊是垂直方向編號爲 2 的網格線, grid-column-end 指定了垂直方向結束邊是垂直方向編號爲 4 的網格線, 因而第二個網格項目的範圍就是 row2, row4, col2, col4 這四條網格線圍成範圍, 這樣一個網格項目就能夠佔據多個網格單元.

可能已經有人注意到 3 和 4 被放到了 2 的上面, 不由會問 3 和 4 是按照什麼規則放的? 這個問題留給以後解釋.

對於這個例子, 咱們還能夠以縮寫的形式, 經過 grid-row grid-column 或者 grid-area 來完成.

.m1 {
	grid-template-columns: repeat(5, 1fr);
	grid-template-rows: repeat(4, 50px);
}
.m1 > .item:nth-child(2) {
	grid-row: 2/4;
	grid-column: 2/4;
}
複製代碼

或者

.m2 {
	grid-template-columns: repeat(5, 1fr);
	grid-template-rows: repeat(4, 50px);
}
.m2 > .item:nth-child(2) {
	grid-area: 2/2/4/4;
}
複製代碼

其中 grid-area 的順序是 grid-row-start grid-column-start grid-row-end grid-column-end.

咱們還能夠省略 grid-column-endgrid-row-end, 這樣的話瀏覽器默認將它們設置成比 start 編號大 1 的網格線, 即只佔一個軌道.

.m1 {
	grid-template-columns: repeat(5, 1fr);
	grid-template-rows: repeat(4, 50px);
}
.m1 > .item:nth-child(2) {
	grid-row-start: 2;
	grid-column-start: 2;
}
複製代碼

img115

能夠看到, 第二個網格項目只佔了一個網格單元.

以上這些網格線相關的屬性還能夠是負值, 表示倒數第 n 條網格線. eg.

.m4 {
	grid-template-columns: repeat(5, 1fr);
	grid-template-rows: repeat(4, 50px);
}
.m4 > .item:nth-child(2) {
	grid-area: -1/2/4/-1;
}
複製代碼

img116

這裏 -1 表示倒數第一條網格線, 因此第二個網格項目是 row5, col2, row4, col6 這四條網格線圍成的區域.

命名的網格線

前面咱們還說了, 網格線除了有本身的編號, 還能夠有本身的名字, 不過編號是瀏覽器定義的, 而名字是開發者本身定義的. 那怎麼定義名字呢? 也很簡單, 在定義網格單元的時候能夠順便定義網格線的名字.

<div class="main m5">
	<div class="item">1</div>
</div>
複製代碼
.m5 {
	grid-template-columns: [first-start] 1fr [first-end second-start] 1fr [second-end third-start] 1fr [third-end];
	grid-template-rows: [first-start] 50px [first-end second-start] 50px [second-end third-start] 50px [third-end];
}
.m5 > .item {
	grid-area: second-start/second-start/second-end/second-end;
}
複製代碼

img117

這裏咱們定義了 3x3 個網格單元, 有 3 列, 每列 1fr 的寬度, 1fr 定義了列寬, 兩邊的 [] 的內容則是軌道兩邊網格線的名字, 而且一個網格線能夠有不止一個名字, 多個名字用空格分隔, 而且不一樣方向的網格線可使用相同的名字, 如垂直方向和水平方向都有名爲 first-start 的網格線. 以後咱們經過名字而不是編號, 指定了網格項目佔據的範圍.

相同名字的網格線

不只不一樣方向的網格線可使用相同的名字, 其實相同方向的網格線也可使用相同的名字.

.m6 {
	grid-template-columns: [col-start] 1fr [col-start] 1fr [col-start] 1fr [col-start];
	grid-template-rows: [row-start] 50px [row-start] 50px [row-start] 50px [row-start];
}
.m6 > .item {
	grid-area: 1/col-start/span 2/col-start 2;
}
複製代碼

img118

這裏咱們的列網格線都叫作 col-start, 行網格線都叫作 row-start, 那瀏覽器要怎麼區分它們? 答案就是 col-start 2 這樣的值, 表示第二條名爲 col-start 的網格線, 也就是編號 2 的網格線. 而若是隻寫 col-start 不加編號的話則默認是第一條 col-start 的網格線.

這裏還有個 span 2, 意思是 start 編號加 2 的網格線, 也就是 1+2=3, 編號 3 的網格線. 因此這裏 grid-area: 1/col-start/span 2/col-start 2; 等價於 grid-area: 1/1/3/2;. 對於只指定告終束網格線, 而起始網格線用 span 的話.

grid-column-end: 5;
grid-column-start: span 2;
複製代碼

這裏的 grid-column-start 應該是 5-2=3, 而不是 5+2=7 了.

以上代碼見 demo.

基於網格區域的定位

以前咱們已經知道什麼是網格區域, 也接觸到了 grid-area, 你會說, 很簡單嘛, grid-area 就是一個縮寫而已, 指定了四條網格線圍成的區域. 不過若是僅僅認爲 grid-area 只有這樣的功能, 那實在是有點 naive 了.

咱們知道網格線能夠有經過名字來引用, 其實網格區域也能夠經過名字引用.

<div class="main m0">
	<div class="item header">1</div>
	<div class="item sidebar">2</div>
	<div class="item content">3</div>
	<div class="item footer">4</div>
</div>
複製代碼
.m0 {
	grid-template-columns: repeat(9, 1fr);
	grid-auto-rows: minmax(100px, auto);
	grid-template-areas: 
	"hd hd hd hd hd hd hd hd hd"
	"sd sd sd main main main main main main"
	"ft ft ft ft ft ft ft ft ft";
}
.m0 > .header {
	grid-area: hd;
}
.m0 > .footer {
	grid-area: ft;
}
.m0 > .content {
	grid-area: main;
}
.m0 > .sidebar {
	grid-area: sd;
}
複製代碼

咱們先定義了 9 列, 而後沒有固定行數, 而是指定了隱式網格的行高最小爲 100px, 最大由瀏覽器自動處理. 接着咱們使用了一個 grid-template-areas 的屬性, 屬性有三個字符串, 映射到三行, 每一個字符串中有 hd 這樣的內容, 空格分隔, hd 表示的是一個網格區域的名字, 每一個 hd 對應一個網格單元, 即指定了這個網格單元屬於 hd 這個區域, grid-template-areas 其實就定義了一個 3x9 的網格系統.

接着咱們定義了四個類 .header .footer .content .sidebar, 而且經過 grid-area 指定了每一個類佔據的區域.

img119

因而咱們經過區域的名字指定了網格項目的範圍.

注意到其實這裏 grid-template-areas 已經完成了劃分網格單元這一操做, 可是它劃分的網格單元都是屬於隱式網格, 由於它沒有指定網格單元的寬高(一個網格單元的寬高都由開發者指定的話才屬於顯式網格), 因此爲了好看點咱們仍是須要 grid-template-columnsgrid-auto-rows.

另外咱們發現, 咱們須要在 grid-template-areas 手寫一個矩陣, 而且每一個份量都必須是一個網格區域的名字, 這會致使網格項目恰好佔滿全部網格單元. 那假如咱們但願留出一些網格單元做爲空白的話怎麼辦? 就像這樣.

img120

咱們能夠這麼寫

.m1 {
	grid-template-columns: repeat(9, 1fr);
	grid-auto-rows: minmax(100px, auto);
	grid-template-areas: 
	"hd hd hd hd hd hd hd hd hd"
	"sd sd sd main main main main main main"
	". . . ft ft ft ft ft ft";
}
.m1 > .header {
	grid-area: hd;
}
.m1 > .footer {
	grid-area: ft;
}
.m1 > .content {
	grid-area: main;
}
.m1 > .sidebar {
	grid-area: sd;
}
複製代碼

咱們用 . 替代了本來是網格區域名字的地方, 表示這裏的網格單元不屬於任何網格區域, 留出空白.

須要注意的是命名的網格區域仍是有它的侷限, 那就是沒法指定兩個網格項目重疊, 若是須要網格項目重疊, 仍是須要經過網格線來實現.

命名網格線定義的命名網格區域

以前咱們的 demo 中, 命名一個網格線都是用 xxx-start, xxx-end 這樣的名字, 是否是必定要用 - 鏈接一個 start 或者 end 呢? 其實也不是, 名字是能夠隨便起的, 不過按照這種方式起名的話有一個好處, 就是瀏覽器會自動幫你定義一個 xxx 的網格區域.

舉個例子.

<div class="main m2">
	<div class="item content">content</div>
</div>
複製代碼
.m2 {
	grid-template-columns: [main-start] 1fr [content-start] 1fr [content-end] 1fr [main-end];
	grid-template-rows: [main-start] 50px [content-start] 50px [content-end] 50px [main-end];
}
.content {
	grid-area: content;
}
複製代碼

img121

能夠看到, 儘管這裏咱們並無定義一個名爲 content 的網格區域, 可是當咱們指定 .content 的網格區域爲 content 時它被放在了中間, 代表確實存在這麼個網格區域, 這個命名的網格區域就是瀏覽器根據命名的網格線自動建立的. 當以 xxx-start 和 xxx-end 命名網格線時, 若是恰好圍成了一個矩形, 則瀏覽器爲這個矩形建立一個名爲 xxx 的網格區域.

命名網格區域定義的命名網格線

一樣的, 瀏覽器也會爲一個命名的網格區域自動建立命名的網格線.

.m3 {
	grid-template-columns: repeat(9, 1fr);
	grid-auto-rows: minmax(100px, auto);
	grid-template-areas: 
	"hd hd hd hd hd hd hd hd hd"
	"sd sd sd main main main main main main"
	"ft ft ft ft ft ft ft ft ft";
}
.m3 > .header {
	grid-row: hd-start/hd-end;
	grid-column: hd-start/hd-end;
}
.m3 > .sidebar {
	grid-row: sd-start/sd-end;
	grid-column: sd-start/sd-end;
}
.m3 > .content {
	grid-row: main-start/main-end;
	grid-column: main-start/main-end;
}
.m3 > .footer {
	grid-row: ft-start/ft-end;
	grid-column: ft-start/ft-end;
}
複製代碼

img122

儘管這裏咱們沒有定義命名網格線, 可是定義了命名的網格區域, 瀏覽器自動爲這些網格區域生成了對應的命名網格線, 也是以 xxx-start, xxx-end 這樣的形式, 因而咱們能夠直接經過這些名字引用這些網格線.

以上代碼見 demo.

網格間距

網格間距比較簡單, 只須要經過 grid-row-gap grid-column-gap 來指定便可.

<div class="main m0">
	<div class="item">1</div>
	<div class="item">2</div>
	<div class="item">3</div>
	<div class="item">4</div>
</div>
複製代碼
.m0 {
	grid-template-columns: repeat(4, 1fr);
	grid-template-rows: repeat(4, 50px);
	grid-row-gap: 10px;
	grid-column-gap: 20px;
}
.m0 > .item:nth-child(1) {
	grid-area: 1/1/3/3;
}
複製代碼

img123

這裏咱們經過 grid-row-gap grid-column-gap 指定了行之間的間距爲 10px, 列之間的間距爲 20px. 須要注意的是, 網格間距並不會致使網格線數量增長.

以上代碼見 demo.

Grid 中的自動定位

以前咱們已經注意到, 有些網格項目儘管咱們沒指定它們佔據的區域, 可是它們仍是會自動按照某種規則進行排列. 如今咱們就來討論這個規則是怎樣運做的.

<div class="main m0">
	<div class="item">1</div>
	<div class="item">2</div>
	<div class="item">3</div>
	<div class="item">4</div>
	<div class="item">5</div>
	<div class="item">6</div>
	<div class="item">7</div>
	<div class="item">8</div>
	<div class="item">9</div>
	<div class="item">10</div>
	<div class="item">11</div>
	<div class="item">12</div>
</div>
複製代碼
.m0 {
	grid-template-columns: repeat(4, 1fr);
	grid-auto-rows: 50px;
}
.m0 > .item:nth-child(2) {
	grid-area: 2/3/4;
}
.m0 > .item:nth-child(5) {
	grid-area: 1/1/3/3;
}
複製代碼

咱們定義了一個 4 列的網格容器, 其中第二個網格項目佔據 row2, row4, col3, col4 圍成的區域, 第五個網格項目佔據 row1, row3, col1, col3 圍成的區域. 以後其餘元素按照先從左往右, 再從上往下的順序進行排列. 如圖所示.

img125

再考慮這種狀況.

.m1 {
	grid-template-columns: repeat(4, 1fr);
	grid-auto-rows: 50px;
}
.m1 > .item:nth-child(2) {
	grid-area: 2/3/4;
}
.m1 > .item:nth-child(5) {
	grid-area: 1/1/3/3;
}
.m1 > .item:nth-child(1) {
	grid-column-end: span 2;
	grid-row-end: span 2;
}
.m1 > .item:nth-child(9) {
	grid-column-end: span 2;
	grid-row-end: span 2;
}
複製代碼

依然是 4 列, 12 個網格項目, 其中 2 和 5 的定位沒變, 1 和 9 沒有指定起始網格線, 只指定告終束網格線, 意味着 1 和 9 要佔據 2x2 個網格單元, 可是具體位置由瀏覽器自動定位. 如圖.

img126

咱們把經過網格線或網格區域指定了位置的網格項目稱爲定位的網格項目, 把沒有指定位置而是依靠瀏覽器自動定位的網格項目稱爲無定位的網格項目. 設定位的網格項目的集合爲 A, 無定位的網格項目的集合爲 B, B[i] 的大小爲 B[i].x, B[i].y, 總行數爲 m, 總列數爲 n, 全部網格單元的集合 C 爲 mxn 的矩陣, 自動定位的算法能夠用僞代碼表述爲這樣

for(i = 0; A不爲空; ++i) {
    將A[i]放到對應位置
}
for(j = 0; j < m; ++j) {
    for(k = 0; k < n; ++k) {
        if(B不爲空) {
            for(p = 0; p < B[0].x; ++p) {
                for(q = 0; q < B[0].y; ++q) {
                    if(c[j+p][k+q]被佔據) {
                        break 2;
                    }
                }
            }
            if(p == B[0].x && q == B[0].y) {
            	cur = B.unshift();
            	把cur放到C[j][k]到C[j+p][k+q]的矩形中
            }
        } else {
            break 2;
        }
    }
}
複製代碼

即先逐行掃描, 找到一個空餘的網格單元, 再從集合 B 中取一個無定位的網格項目, 看是否可以放下, 不能的話就找下一個空餘網格單元, 重複此操做.

因此在這種狀況下, 3 沒有在 2 的上面, 而是和 2 相鄰, 2 和 3 上面留出了空白.

固然咱們也能夠把這些空白填上, 只須要經過 grid-auto-flow: dense; 便可, 沒錯, 就是以前咱們見到的那個改變自動定位流向的 grid-auto-flow.

.m2 {
	grid-template-columns: repeat(4, 1fr);
	grid-auto-rows: 50px;
	grid-auto-flow: dense;
}
.m2 > .item:nth-child(2) {
	grid-area: 2/3/4;
}
.m2 > .item:nth-child(5) {
	grid-area: 1/1/3/3;
}
.m2 > .item:nth-child(1) {
	grid-column-end: span 2;
	grid-row-end: span 2;
}
.m2 > .item:nth-child(9) {
	grid-column-end: span 2;
	grid-row-end: span 2;
}
複製代碼

img127

能夠看到, 如今 3, 4, 6 把以前 2 上面和邊上的空餘空間給補上了. 這種狀況下的自動定位算法能夠描述爲

for(j = 0; j < m; ++j) {
    for(k = 0; k < n; ++k) {
        for(i = 0; B不爲空;) {
            for(p = 0; p < B[i].x; ++p) {
                for(q = 0; q < B[i].y; ++q) {
                    if(c[j+p][k+q]被佔據) {
                        break 2;
                    }
                }
            }
            if(p == B[i].x && q == B[i].y) {
                cur = B.splice(i, 1);
                把cur放到C[j][k]到C[j+p][k+q]的矩形中
                i = 0;
            } else {
                ++i;
            }
        }
    }
}
複製代碼

即先逐行掃描, 找到一個空餘的網格單元, 再從集合 B 中取一個非定位的網格項目, 看是否可以放下, 若是不能, 則從 B 中取下一個非定位的網格項目, 重複此操做. 這種狀況下, 會盡可能保證從上往下沒有空白.

注意這裏都沒有改變自動定位的流向, 即默認是行優先, 先從左往右, 再從上往下, 逐行掃描. 若是改變了自動定位的流向, 即 grid-auto-flow: column; 或者 grid-auto-flow: column dense;, 則算法變爲列優先, 即先從上往下, 再從左往右, 逐列掃描.

其實上面隱含了一個小技巧, 就是大部分時候若是咱們但願指定一個網格項目的大小, 那就得經過網格線或者網格區域, 不可避免地也指定了網格項目的位置, 假如咱們但願指定一個網格項目的大小, 但又不想指定它的具體位置, 咱們能夠省略起始網格線, 經過 span 關鍵字, 如 grid-template-columns: span 2; 這樣, 就指定了一個兩列寬的網格項目, 但又沒有指定它的具體位置, 而是由瀏覽器自動定位.

以上代碼見 demo.

匿名網格項目

如同匿名塊級元素和匿名行內元素同樣, 當出現這種狀況的時候

<div class="grid">
    test test
    <div class="item">item</div>
    <div class="item">item</div>
</div>
複製代碼

未被標籤包裹的 test test 內容也會被做爲網格項目, 瀏覽器會爲它生成一個匿名網格項目, 匿名網格項目只能被自動定位, 由於咱們沒法經過選擇器爲它們指定位置.

absolute 和 grid

默認狀況下, 若是一個網格項目是絕對定位的話, 同其餘時候同樣, 網格項目脫離文檔流, 瀏覽器不會爲它生成隱式網格, 它也不會影響其餘網格的定位, 就像它不是網格容器的子元素那樣.

<div class="main m0">
	<div class="item">1</div>
	<div class="item">2</div>
	<div class="item">3</div>
	<div class="item">4</div>
</div>
複製代碼
.m0 {
	grid-template-columns: 1fr 1fr 1fr;
	grid-template-rows: 50px 100px;
}
.m0 > .item:nth-child(4) {
	width: 50px;
	height: 50px;
	position: absolute;
	top: 0;
	left: 0;
}
複製代碼

img128

能夠看到, 4 是絕對定位的, 由於網格容器沒有設置任何定位方式, 因此這裏它的包含塊是根元素而不是網格容器.

而假如網格容器設置了定位, 且絕對定位的元素本來是自動定位的. 以下面例子.

.m1 {
	grid-template-columns: 1fr 1fr 1fr;
	grid-template-rows: 50px 100px;
	position: relative;
}
.m1 > .item:nth-child(4) {
	width: 50px;
	height: 50px;
	position: absolute;
	top: 0;
	left: 0;
}
複製代碼

img129

對於設置了絕對定位的自動定位的網格項目, 若是網格容器設置了定位, 則包含塊是網格容器.

假如咱們給絕對定位的網格項目設置了網格區域

.m2 {
	grid-template-columns: repeat(4, 1fr);
	grid-template-rows: repeat(4, 50px);
	position: relative;
}
.m2 > .item:nth-child(4) {
	width: 50px;
	height: 50px;
	grid-area: 2/2/4/4;
	position: absolute;
	bottom: 0;
	left: 0;
}
複製代碼

img130

則網格項目的包含塊是網格區域而不是網格容器. 能夠看到, 4 位於網格區域的左下角而不是網格容器的左下角.

img131

這幅圖可以很清楚地說明這點.

以上代碼見 demo.

grid 中的對齊

同 flexbox 同樣, grid 中也有對齊, 依舊是熟悉的屬性, 熟悉的味道.

  • align-items
  • align-self
  • align-content
  • justify-self
  • justify-content

在這裏, 咱們先糾正兩點比較容易引發誤會的地方.

  • grid 的主要做用是定位而不是控制網格項目的大小, 儘管它能夠用來控制網格項目的大小. 咱們經過給網格項目指定網格線/網格區域, 實際上是指定網格項目在這塊區域內定位, 而不是指定網格項目佔滿這塊區域
  • 並非整個網格容器都會被網格軌道佔滿

實際上, 網格項目的大小是由以上屬性來決定的.

這部份內容對於熟悉 flexbox 的人來講比較簡單, 就只貼幾個簡單的示例好了, 若是不熟悉的話建議先了解 flexbox.

須要注意的是, grid 和 flexbox 不同的是, grid-auto-flow 不會像 flex-direction 那樣改變 start 和 end. 好比即便 grid-auto-flow: column; 的狀況, align-items 仍是對每行的行內進行上下對齊, 而不會變成對每列列內進行左右對齊. 同理其餘屬性.

align-items

控制每行中的全部網格項目在行軌道內的對齊方式

align-items: start

img132

align-items: end

img133

align-items: center

img134

align-items: baseline

img135

以上代碼見 demo.

align-self

控制單個元素在行軌道內的對齊方式

align-self: end

img136

align-self: center

img137

align-self: baseline

img138

以上代碼見 demo.

align-content

控制全部行軌道在網格容器內的對齊方式

.m0 {
	width: 500px;
	height: 400px;
	grid-template-columns: repeat(3, 100px);
	grid-template-rows: repeat(3, 100px);
	align-content: start;
}
.m0 > .item:nth-child(2) {
	font-size: 28px;
}
複製代碼

注意到, 全部的列寬之和小於網格容器的寬度, 全部的行高之和小於網格容器的高度.

img139

因而整個網格系統沒有佔滿網格容器, 而是位於網格容器左上角, 這是 align-content: start; 的做用.

align-content: end

img140

align-content: center

img141

align-content: space-around

img142

align-content: space-between

img143

注意, align-content 可能會使得行間距變寬.

以上代碼見 demo.

justify-self

相似 align-self, 控制一個網格項目在列軌道上的對齊方式.

justify-self: start

img144

justify-self: end

img145

justify-self: center

img146

以上代碼見 demo.

justify-content

相似 align-content, 控制全部列軌道在網格容器內的對齊方式

justify-content: end

img147

justify-content: center

img148

justify-content: space-around

img149

justify-content: space-between

img150

注意, justify-content 可能致使列間距變寬.

以上代碼見 demo.

Grid 相關屬性

  • grid-template-columns
  • grid-template-rows
  • grid-template-areas
  • grid-template
  • grid-auto-columns
  • grid-auto-rows
  • grid-auto-flow
  • grid-row-start
  • grid-column-start
  • grid-row-end
  • grid-column-end
  • grid-row
  • grid-column
  • grid-area
  • grid-row-gap
  • grid-column-gap
  • grid-gap
  • grid

看上去不少, 不過如今咱們能夠把它們劃分紅幾類:

  • 劃分網格單元的屬性
  • 定位網格項目的屬性

grid-template-columns/grid-template-rows

劃分網格單元的屬性, 做用於網格容器, 用來指定網格的列寬列數以及命名網格線, 默認值 none. eg.

grid-template-columns: 50px 100px;
grid-template-columns: [col-start] 100px;
grid-template-columns: [col-start] 100px [col-end];
grid-template-columns: [col-start] 100px [col-end col-start] 100px [col-end];
grid-template-columns: 50px minmax(30px, 1fr);
複製代碼

百分比的值相對於網格容器的 content-box, 若是網格容器的 content-box 大小是由軌道的大小決定的, 則百分比單位被視爲 auto.

grid-template-areas

劃分網格單元的屬性, 做用於網格容器, 用來命名網格區域, 默認值 none. eg.

grid-template-areas: "a a a b" "a a a b";
grid-template-areas: "a a a b" ". . . b";
複製代碼

grid-template

劃分網格單元的屬性, 做用於網格容器, 是 grid-template-columns grid-template-rows grid-template-areas 的縮寫.

grid-template: 100px 1fr/50px 1fr; /* rows / columns */
grid-template: [row-line] 100px [row-line] 50px [row-line]/[col-line] 50px [col-line] 50px [col-line];
grid-template: "a a a" "b b b";
grid-template: "a a a" 20px "b b b" 30px;
grid-template: "a a a" 20px "b b b" 30px / 1fr 1fr 1fr;
grid-template: [header-top] "a a a" [header-bottom] [main-top] "b b b" 1fr [main-bottom] / auto 1fr auto;
複製代碼

grid-auto-columns/grid-auto-rows

劃分網格單元的屬性, 做用於網格容器, 用來指定隱式網格的行列寬度.

grid-auto-columns: 1fr;
grid-auto-columns: 1fr 100px; /* 1fr 100px 1fr 100px... */
grid-auto-columns: 1fr 100px 50px; /* 1fr 100px 50px 1fr 100px 50px... */
複製代碼

百分比的值相對於網格容器的 content-box, 若是網格容器的 content-box 大小是由軌道的大小決定的, 則百分比單位被視爲 auto.

grid-auto-rows / grid-auto-rows 能夠不止一個值, 多個值表示交替.

grid-auto-flow

劃分網格單元的屬性, 做用於網格容器, 用來指定自動定位算法是行優先(先從左往右再從上往下)仍是列優先(先從上往下再從左往右)以及是否填充空餘空間. 默認值 row.

grid-auto-flow: row;
grid-auto-flow: column;
grid-auto-flow: row dense;
grid-auto-flow: column dense;
複製代碼

grid-column-gap/grid-row-gap

劃分網格單元的屬性, 做用於網格容器, 用來指定垂直/水平方向的網格間距大小. 默認值 0.

grid-column-gap: 20px;
grid-column-gap: 20%;
複製代碼

百分比相對於網格容器的 content-box.

grid-gap

劃分網格單元的屬性, 做用於網格容器, grid-column-gap grid-row-gap 的縮寫.

grid-gap: 10px 20px; /* row col */
複製代碼

grid

grid-template-rows grid-template-columns grid-template-areas grid-auto-rows grid-auto-columns grid-auto-flow 的縮寫.

grid-row-start/grid-row-end/grid-column-start/grid-column-end

定位網格項目的屬性, 做用於網格項目, 指定起始/結束的網格線.

grid-row-start: 3;
grid-row-start: row-start;
grid-row-start: row-start 2;
grid-row-start: span 3;
複製代碼

grid-row/grid-column

grid-row-start grid-row-end / grid-column-start grid-column-end 的縮寫.

grid-row: 1/3; /* start/end */
grid-column: 2/4;
複製代碼

grid-area

定位網格項目的屬性, 做用於網格項目, 指定網格項目所屬的網格區域.

grid-area: 1/2/3/4; /* row-start/column-start/row-end/column-end */
grid-area: row-start/column-start/row-end/column-end;
grid-area: areaname;
複製代碼

幾個函數

  • repeat()
  • minmax()
  • fit-content()

fit-content()

適用於任何能夠用來定義軌道寬度的屬性. 用來指定軌道的寬度, 效果相似於 max-width, 即內容不夠給定寬度的話, 按內容寬度, 內容大於給定寬度, 按給定寬度. eg.

grid-template-columns: fit-content(20px);
grid-template-columns: fit-content(20%);
grid-template-columns: fit-content(20vw);
複製代碼

其中百分比單位是, 在空間足夠時, 相對於網格容器 content-box 對應軸方向的大小, 在空間不夠時, 相對於網格容器 content-box 對應軸方向的剩餘空間的大小(即 content-box 大小減去全部網格間距的大小).

以上代碼見 demo.

minmax()

適用於任何能夠用來定義軌道寬度的屬性. 用來指定軌道的最小和最大寬度, 效果相似於同時指定了 min-widthmax-width. 帶兩個參數 min 和 max. eg.

grid-template-columns: minmax(100px, 200px);
grid-template-columns: minmax(100px, 1fr);
grid-template-columns: minmax(100px, 20%);
grid-template-columns: minmax(100px, min-content);
grid-template-columns: minmax(100px, max-content);
grid-template-columns: minmax(100px, auto);
複製代碼

幾個注意點

  • 若是 min > max, 則忽略 max 的值, 整個 minmax(min, max) 的值視爲 min 的值
  • fr 單位能夠做爲 max, 可是不能做爲 min
  • 百分比單位相對於網格容器的 content-box 大小
  • max-content 表示儘量寬, 儘量不換行
  • min-content 表示儘量窄, 達到最大的 inline-box 寬度
  • auto 做爲 max 時等同於 max-content, 做爲 min 時, 等同於 min-content. 實際測下來, auto 在 max 下的表現和 1fr 同樣, 不知道爲何

示例代碼見 demo

repeat()

適用於任何能夠用來定義軌道寬度的屬性. 用於重複某一模式生成軌道列表. 效果相似於宏展開或 Sass 的 @mixin. eg.

grid-template-columns: repeat(2, 20%);
grid-template-columns: repeat(2, 1fr); /* 1fr 1fr */
grid-template-columns: repeat(2, [col-start] 1fr); /* [col-start] 1fr [col-start] 1fr */
grid-template-columns: repeat(2, [col-start] 1fr [col-end]); /* [col-start] 1fr [col-end col-start] 1fr [col-end]*/
grid-template-columns: repeat(2, [col1-start] 1fr [col2-start] 3fr); /* [col1-start] 1fr [col2-start] 3fr [col1-start] 1fr [col2-start] 3fr */
grid-template-columns: repeat(2, [col-start] minmax(100px, 1fr) [col-end]);
grid-template-columns: repeat(auto-fill, 100px);
grid-template-columns: repeat(auto-fit, 100px);
複製代碼

重點

  • 百分比單位相對於網格容器的 content-box 大小
  • auto-fill 會盡量地讓每一個軌道更寬而且儘量在一行(列)中放下更多列(行), 不保證全部軌道佔滿網格容器. 在每一個軌道寬度肯定的狀況下優先確保軌道盡量寬(示例 m1), 在每一個軌道寬度不肯定的狀況下, 優先確保放下更多列(示例 m2, m3, m4). (不要看 MDN 的描述!!! 巨他媽難翻譯! 看 W3C 的描述 好懂得一比, 其中的 gap 不是指 grid-gap, 而是網格系統沒佔滿網格容器的空餘空間都是 gap)
  • auto-fit 會盡量讓全部軌道佔滿網格容器, 而且儘量地讓每一個軌道更寬而且儘量在一行(列)中放下更多列(行).
  • 我以爲實際場景下, 只要用 auto-fit 就夠了...

示例代碼見 demo

其餘一些細節

  • floatclear 對網格項目是無效的, 可是 float 依然會改變網格項目 display 的計算值(參考 深刻理解float), 網格項目的 display 計算值默認爲 blockified(The display value of a grid item is blockified: if the specified display of an in-flow child of an element generating a grid container is an inline-level value, it computes to its block-level equivalent)
  • vertical-align 對網格項目是無效的
  • ::first-line ::first-letter 不能用於網格容器
  • z-index 用來控制網格項目之間的疊加層級
  • order 用來控制自動定位的網格項目的排列順序
  • 利用媒體查詢能夠動態地改變列數和行數

參考資料

相關文章
相關標籤/搜索