CSS Grid 網格佈局全攻略

所有奇技淫巧都只在方寸之間。

幾乎從我們踏入前端開發這個領域開始,就不停地接觸不同的佈局技術。從常見的浮動到表格佈局,再到如今大行其道的flex佈局,css佈局技術一直在不斷地推陳出新。其中網格佈局(grid)作爲css3的產物,它更加貼近網頁設計師所使用的佈局策略,學習並利用好它可以讓我們免受很多佈局困擾。

雖然網格佈局好處有很多,但學習起來並不簡單,原因是用來設置佈局的屬性實在太多,其中光是作用於父容器的屬性就有17種,再加上子元素屬性有10種,另外還有這些屬性值的不同取值方式。這些對於記憶來說絕對是個不小的負擔。那麼這麼多屬性以及用法,要如何在短時間內消化掉呢?在接下來這篇文章裏,我將針對這27種屬性以及它們各自的用法,分享我獨家的學習策略,希望對大家的學習有所幫助。

佈局之道

CSS作爲一種網頁排版設計語言,其核心的設計思想必然要遵守相關的領域知識。網格佈局是一種二維佈局結構,它是由縱橫相交的兩組網格線形成的框架性佈局結構。網頁設計者可以利用這些由行(row)和列(column)形成的框架性結構來佈局設計元素。
在定義一種網格佈局結構的時候,我們需要在父容器上描述要佈局的主體框架結構。爲了描述這一框架結構,我們就需要給它的基本構成元素命名。一個網格佈局的構成元素可以概括爲以下幾種概念:

  • row line: 行線
  • column line: 列線
  • track: 網格軌道,即行線和行線,或列線和列線之間所形成的區域,用來擺放子元素
  • gap: 網格間距,行線和行線,或列線和列線之間所形成的不可利用的區域,用來分隔元素
  • cell: 網格單元格,由行線和列線所分隔出來的區域,用來擺放子元素
  • area: 網格區域,由單個或多個網格單元格組成,用來擺放子元素


牢記上述這些概念是之後熟練掌握和應用網格佈局的基礎。

構建之法

要熟練掌握一門技術,核心是找到最基本的套路,然後不斷練習從而可以在之後的實踐過程中減少決策的時間。所以,這一部分主要就是介紹網格佈局構建過程中的一些常用套路。
這裏我們要解決的問題是,如何利用最基本的規則來構建出理想的佈局模型。在佈局過程中,歸根結底需要處理的就兩種頁面元素:父容器和子元素。前者主要用來設置基礎的佈局框架,相當於建築中的設計藍圖,而後者就是用來進行個性化的佈局調整。因此我個人歸納了在使用網格佈局過程中的套路是:針對父容器元素進行設置需要三個步驟:定框架、設間隔和找對齊,對子元素來說有兩個步驟:擺位置和找對齊。我把它們統稱爲"32構建之法"

在這一小節中,我將把重心主要放在網格佈局中所有用到的27個屬性名的講解上,而取值邏輯將在最後一部分進行統一介紹。

父容器

定框架

設置父容器的網格佈局的第一步就是將父容器的盒模型設置爲grid,這樣就能觸發渲染引擎的網格佈局算法。

.parent {
    display: grid;
}

接着我們要開始準備"畫線",即設置所需行和列的基礎線。這些線條將構成我們接下來進行佈局排布的基礎模板(template)。在畫線過程中,我們需要分別根據行(row)和列(column)兩個維度進行設置。你需要畫幾條線,就設置幾個值(不包括邊框),其取值是軌道(track)的大小。這裏我先畫出一個3x3的網格框架,代碼如下:

.parent {
    display: grid;
    grid-template-rows: 100px 100px 100px;
    grid-template-columns: 100px 100px 100px;
}

在這裏你也可以選擇使用縮寫形式同時爲行和列設置值,採用/分隔開:

.parent {
    display: grid;
    grid-template: 100px 100px 100px / 100px 100px 100px;
}

畫完線後,下一步我們可以選擇爲這些線條和線條之間形成的網格區域(area)進行命名,這樣在後續使用的時候就能直接使用這些名字,便於子元素的定位。

.parent {
    display: grid;
    grid-template-areas: "a a b"
                         "c d e"
                         "c d ."
}

上面這兩步畫線和命名同樣可以採用縮寫形式進行設置,代碼如下:

.parent {
    display: grid;
    grid-template: "a a b" 101px
                   "c d e" 102px
                   "c d ." 103px / 104px 105px 105px
}

因爲使用grid-template同時設置行列和區域名的寫法比較複雜,爲了講解方便,我把值設置成規律的遞增數字。其中(101, 102,103)設置的是grid-template-rows的值,而(104,105,106)設置的則是grid-template-columns的值。

到這一步,我們可以說已經完成所需工作的一大半了。

設間隔

間距(gap)的設置在實際開發中是可選的,主要根據網頁設計的需求而定。如果你需要給網格線之間設置間距,我們可以在行列兩個維度上分別進行設置, 下面這段代碼將給每個行和列分別設置10px的間隔:

.parent {
    display: grid;
    grid-template: 100px 100px 100px / 100px 100px 100px;
    grid-row-gap: 10px;
    grid-column-gap: 10px;
}

如果採用縮寫形式,上述代碼又可以簡化成:

.parent {
    display: grid;
    grid-template: 100px 100px 100px / 100px 100px 100px;
    grid-gap: 10px 10px;
}

設置後的效果如下:

找對齊

有了前面兩個步驟,我們的網格佈局框架基本上算是搭建得差不多了。每個子元素都會默認佔據一個網格區域。而在父容器這裏我們如果有需要,就要進行最後一個步驟:找對齊。所謂對齊方式,可以分爲內部和外部兩種(前者是針對每個網格區域的子元素而言,而後者是相對於網格區域本身)。另外在行和列(更專業的術語是main axis和cross axis)上又各自有兩個維度,這就構成了4種設置對齊的方式。

先來處理一下每個子元素相對網格區域內部的對齊方式:

.parent {
    display: grid;
    grid-template: 100px 100px 100px / 100px 100px 100px;
    grid-gap: 10px 10px;
    justify-items: center;
    align-items: center;
}

在上面的代碼中我分別在行和列方向上都設置了居中對齊,這樣每個網格區域中的子元素相對於各自的區域行爲是一致的,都能均勻排布。可以看到效果如下圖所示:

再來看一下另一種情況:

.parent {
    display: grid;
    width: 500px;
    height: 500px;
    grid-template: 100px 100px 100px / 100px 100px 100px;
    grid-gap: 10px 10px;
    justify-content: space-between;
    align-content: center;
}

有時候我們設置的網格不足以覆蓋整個父容器的大小時,比如在上述的例子中整個父容器有500px*500px的大小,而我們只設置了300px*300px的網格區域,這時候就需要指定多出來空間的處理規則。justify-contentalign-content就是分別在行和列兩個方向上用來解決這個問題的屬性。它們將針對每個網格區域去設置其在父容器中的對齊方式。

justifyalign這兩個單詞在方向上比較容易搞混,所以我在記憶上採取的方式是記住justifyrowaligncolumn這兩個合併詞,它們長度差不多。如果你有更好的記憶方式,請留言告訴我。

子元素

我們通過在父容器上搭建好了基礎的框架後,對於大部分子元素來說,就已經能夠很好地滿足佈局要求了。針對部分子元素,可以根據需求進行微調。如果要在子元素上進行佈局微調,通常需要以下兩個步驟:擺位置和找對齊

擺位置

像下棋一樣,針對子元素的排布,我們需要給它們指定要擺放的具體位置。要確定具體位置,可以利用之前在父容器中所指定的線名區域名來定位。一種方式是直接通過設置起始行,結束行和起始列,結束列來給子元素劃定它所要擺放的區域,另外一種方式是指定要擺放的區域名。

/* 指定起始行,結束行,起始列,結束列 */
.child:first-child {
    grid-row-start: 1;
    grid-row-end: 2;
    grid-column-start: 1;
    grid-column-end: 3;
    background: red;
}

/* 使用縮寫形式 */
.child:nth-child(2) {
    grid-row: 2/3;
    grid-column: 2/4;
    background: yellow;
}

/* 直接指定區域名 */
.child:nth-child(3) {
    grid-area: i;
    background: green;
}

這段代碼的效果如下:


還有一種更加靈活的設置位置方式,是指定跨越的行數和列數,關鍵字span用來控制一次跨越的行數或列數, 如上面第三個子元素可以改寫成:

.child-nth-child(3) {
  grid-row: 2/3;
  grid-column: span 2;
}

找對齊

和父容器中所設置的對齊方式類似,針對個別子元素的對齊處理,我們可以按照行列兩組屬性進行分別處理:

/* 列對齊 */
.child:nth-child(1) {
  align-self: end;
}

/* 行對齊 */
.child:nth-child(2) {
  justify-self: end;
}

/* 採用縮寫形式 */
.child:nth-child(3) {
  place-self: center center;
}

*隱式網格

靈活性是網格佈局的一大優勢,除了採用上述那種手動指定框架結構的方式,網格佈局還有一套自動化佈局的機制,這套機制稱爲“隱式網格佈局”。當我們在網格定義的區域外放置子元素時,或因子元素數量過多而需要更多的網格線時,佈局算法就會自動生成隱式網格。默認情況下這些隱式網格的大小也會隨着內容尺寸不同而變化,而我們可以利用屬性grid-auto-rowsgrid-auto-columns來控制隱式網格的大小。
考慮下面這個例子:

<div class="parent">
  <div class="child" style="background: red"></div>
  <div class="child" style="background: yellow"></div>
  <div class="child" style="background: green"></div>
</div>
.parent {
  display: grid;
  grid-auto-rows: 100px;
  grid-auto-columns: 100px;
}

通過手動在父容器中設置隱式網格大小爲100x100的大小後,效果如下:


如果子元素引用了不存在的行號和列號,父容器會自動生成隱式網格以容納所有子元素:

.child:first-child {
    grid-row-start: 1;
    grid-row-end: 2;
    grid-column-start: 1;
    grid-column-end: 3;
    background: red;
}

有了網格大小的控制,我們還需要位置的控制。默認情況下,子元素都是先將行填充滿,容器大小不夠的時候纔會生成新的隱式行。如果要改變這一默認行爲,我們需要使用grid-auto-flow屬性來控制:

.parent {
  display: grid;
  grid-auto-rows: 100px;
  grid-auto-columns: 100px;
  grid-template-areas: "a b c" "d e f" "g h i";
  grid-auto-flow: column;
}

取值之術

介紹完所有的網格佈局屬性後,我們再來說一下各種屬性的取值策略。

大小

在CSS中,我們通常使用px,em等取值單位進行屬性大小的設置,對於靈活的佈局需求來說,百分比也是常用的取值單位。這些單位在平常工作中似乎已經足夠用了。不過,爲了讓佈局能夠更加靈活,網格佈局中引入了一種新單位fr,它是fraction這個單詞的縮寫,意思是容器內剩餘空間的分數比。考慮下面這個例子:

.parent {
  height: 100px;
  display: grid;
  grid-template-columns: 100px 1fr 100px;
}

我們通過設置100px 1fr 100px的佈局框架,從而很輕鬆地就實現了兩邊寬度固定,中間自適應的效果。

如果要實現有比例關係的佈局結構,還可以使用多個fr的取值:

.parent {
  width: 400px;
  height: 100px;
  display: grid;
  grid-template-rows: 100px 1fr 100px;
  grid-template-columns: 1fr 1fr 2fr;
  grid-template-areas: "a b c"
}

可以看到區域a,b,c之間的比例關係就是"1:1:2"的關係。


除了上述這個支持自適應的單位外,網格佈局中還能夠使用max-contentmin-content這組關鍵字來達到自適應的目的。要理解這兩個關鍵字,首先需要理解內在尺寸(intrinsic size)和外部尺寸(extrinsic size)這兩個概念。先說一下extrinsic size,它的相對值計算是相對於父容器對應的屬性值。我們知道,width如果使用百分比單位,其計算值是相對於該元素所在的容器寬度的,比如父容器寬度100px, 子元素設置width: 20%,那麼它的寬度就是100px * 20% = 20px。在css3中引入了intrinsic size則是相對於元素自身尺寸進行計算。max-contentmin-content就是相對於元素自身內容塊進行計算的屬性值。

min-content顧名思義是根據元素內容來設置的最小寬度大小,在英文句子中,通常是最長單詞的那個長度,而中文中則是一個字的長度。比如下面這個例子:

.parent {
  display: grid;
  grid-template-columns: auto min-content auto;
}

可以看到,中間那個網格的那個寬度就等於scq000這個單詞的長度。

min-content相對應,max-content會將尺寸設置成內容尺寸能達到的最大寬度。我們把代碼改成下面這樣:

.parent {
  display: grid;
  grid-template-columns: auto max-content auto;
}

有了這兩個屬性值,我們可以很容易地讓佈局區域根據內容進行自適應。

函數

函數是用來避免重複性工作的一種有效工具,在網格佈局中提供了一些常用CSS函數來方便我們的工作。

第一個要介紹的是minmax這個函數。在設置網格框架的過程中,對於自適應的網格區域,我們都會設置一個最小值和最大值,這個函數就是用來實現這個目的的。

.parent {
    display: grid;
    grid-template-columns: 100px minmax(100px, 200px) 100px;
}

/* 最常用的情況是隻設置最小,不設置最大值 */
.parent {
    display: grid;
    grid-template-columns: 100px minmax(100px, auto) 100px;
}

利用這個函數設置的網格佈局可以做到很好的自適應,在頁面伸縮過程中也能保證佈局的穩定性。

另一個很有用的函數是fit-content,它實際上是min(maximum size, max(minimum size, argument))的簡寫,表示將元素寬度收縮到內容寬度。說得通俗點就是,使用這個函數後會儘量不佔用多餘的空間。如果內容的寬度小於fit-content中設置的長度,那麼實際子元素寬度是內容寬度。如果內容寬度超出了fit-content中設置的長度,那麼實際子元素寬度就是設置的那個長度。下面看兩個例子:

.parent {
    display: grid;
    grid-template-rows: auto fit-content(200px) auto;
}

第一個句子中的長度超出了200px,那麼此時中間網格的寬度是200px。而第二個例子中內容寬度不足200px,此時中間網格的寬度是句子實際佔用的寬度。

最後要介紹的是repeat函數,它主要用來批量設置框架的間距,這個函數接受兩個參數,第一個參數控制循環次數,第二個參數控制間距大小。讓我們用這個函數改寫一下上述例子:

.parent {
    display: grid;
    grid-template-rows: 100px 100px 100px;
    grid-template-columns: 100px 100px 100px;
}
/* 利用repeat函數改寫 */
.parent {
    display: grid;
    grid-template-rows: repeat(3, 100px);
    grid-template-columns: repeat(3, 100px);
}

另外,第一個參數除了可以使用數字顯示設置網格數量外,還能使用auto-fitauto-fill兩個關鍵字自動分配空間。通常情況下,這兩個關鍵字的使用效果都差不多,唯一的差別是空餘空間的分配規則。搭配minmax函數可以看出區別,如下面這兩個例子:

.parent {
    display: grid;
    width: 500px;
    height: 100px;
    grid-gap: 10px;
    grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
}

.parent {
    display: grid;
    width: 500px;
    height: 100px;
    grid-gap: 10px;
    grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
}

在單行佈局的時候,如果有空餘空間auto-fit會將它們平均分配到所有子元素中,而auto-fill會自動創建空白的列。

命名

我們在創建網格佈局框架的時候,通過"畫線"來指定基本佈局結構。默認情況下,網格佈局會給每一條網格線進行命名,命名順序同書寫順序一致:從左到右,由上至下按數字命名。假設我們指定的是3x3的網格佈局結構,那麼包含邊框線,就會生成4+4=8條線。



除了採用默認命名方式,我們還能夠自定義網格線的名稱以便於後續在子元素定位中使用。

.parent {
  display: grid;
  grid-template-rows: [row-a] 100px [row-b] 100px [row-c] 100px [row-d];
}

對齊

對齊是佈局過程中一個不可缺少的步驟,它的取值是通過已有的關鍵字來指定的。其中用於網格佈局中對齊的關鍵字有start,center, endstretch四個。
我們先從默認值stretch看起,這個關鍵字是伸展的意思,所以在默認情況下網格中的子元素會儘可能地填充滿網格區域,由於是默認值所以平常寫代碼的時候就不太會可以去用這個關鍵字進行聲明。
接着再來看一下常用的對齊取值策略,只需要記住一點就可以了:用屬性justify-*,align-*來分別控制橫軸和縱軸兩個方向,屬性值控制其對齊位置。 startcenterend三個屬性值就分別對應了前中後三個位置。

.parent {
  display: grid;
}
.child:first-child {
  justify-self: start;
}
.child:first-child {
  justify-self: center;
}
.child:first-child {
  justify-self: end;
}

*排列

讓我們重新回到隱式網格那部分,隱式的排列規則是通過指定grid-auto-flow這個屬性來設置的。它的取值只有三個rowcolumndense。上面屬性部分已經介紹過rowcolumn兩個屬性值,前者是按行優先來擺放子元素,後者是按列優先來擺放子元素。這裏主要介紹一下dense這個屬性值。通常情況下,排列子元素會按照順序填充行或列,如果空間不足的時候,會換行或換列。而使用了dense屬性後,會盡量使用空餘空間,考慮下面這段代碼:

.parent {
  display: grid;
  grid-template-columns: repeat(3, 100px);
}
<div class="parent">
  <div class="child">1</div>
  <div class="child">2</div>
  <div class="child">3</div>
  <div class="child">4</div>
  <div class="child">5</div>
</div>

默認情況下,顯示效果是這樣的:


當我們使用dense值進行排列的時候,就相當於開啓“緊湊”模式,會儘可能利用空餘空間。

.parent {
    display: grid;
    grid-auto-flow: row dense;
    /* 由於默認爲按行排列,可省略爲dense */
    grid-auto-flow: dense;
}

所以,顯示效果如下:


總結

在這篇文章裏,我通過拆解分類所有網格佈局相關的知識點,希望能夠爲大家提供一個比較系統的運用網格佈局的指導方法。在實際應用過程中,沿着"32構建之法”的這個套路來走,可以節約很多思考和決策時間。另外,由於這篇文章信息密度可能比較大,希望大家能夠多多複習,並跟着例子實際操練幾遍,這樣在實際工作運用中才能如魚得水。最後,送上一張思維導圖,幫助大家能夠一覽本文所有的重點。

推薦閱讀

http://grid.malven.co/

https://www.w3.org/TR/css-align-3/

https://medium.com/@patrickbrosset/demystifying-css-alignment-2d3ea7a02a36

https://www.zhangxinxu.com/wordpress/2016/05/css3-width-max-contnet-min-content-fit-content/
https://www.mozilla.org/en-US/firefox/channel/desktop/
https://css-tricks.com/difference-explicit-implicit-grids/

畫圖調試工具: https://www.mozilla.org/en-US/firefox/channel/desktop/

——本文首發於個人公衆號,轉載請註明出處———


最後,歡迎大家關注我的公衆號,一起學習交流。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章