JavaScript五十問——對比來說CSS的Grid與FlexBox(下篇)

前言

在上篇——JavaScript五十問——對比來說CSS的Grid與FlexBox(上篇),我介紹了Flex的屬性與使用,今天我們來總結一下Grid的具體使用方法,最後會結合Flex與Grid佈局講一講二者的聯繫與不同。

需要注意得是,Grid佈局與我們之前所熟悉的css佈局思路有很大的不同,閱讀這篇文章之前,需要把我們平時對css的刻板印象拋棄掉,準備接受知識的洗禮吧,少年!

Grid

與 Flex 相同,Grid 也分爲容器與元素兩個概念;在一個 html 標籤中添加樣式:display:grid或者display:inline-grid,即構建了一個 Grid 的容器,裏面的 dom 元素即爲 Grid 元素。同樣,Grid 也分爲兩類屬性,分別裝載在容器與元素上,下面一一說明。

HTML結構說明

以下所有例子均基於或擴展於下面的HTML結構:

<style>
    .container{
      width:500px;
      background-color:#999;
    }
    .item{
      width: 50px;
      background-color:#567;
      color:#fff;
    }
  </style>
 <div class="container">
    <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>

Grid 基本概念介紹

網格

Grid容器裏面有網格一系列的概念;聽着唬人,但是結合圖很容易理解。

網格線

水平方向有垂直方向的線段即爲網格線
clipboard.png

網格軌道

兩個相鄰的平行網格線之間的區域就是網格軌道
clipboard.png

網格單元

四個相鄰邊組成的區域就是網格單元。
clipboard.png

fr

fr是Grid中特有表示尺寸的單位,是分數——fraction的縮寫,假設我們現在有四個grid元素,每個元素的寬度都是1fr,那麼每個元素的實際寬度就是總寬度的1/4。

fr也可以跟%,px 共同使用,他的計算規則就是刨去px與%的剩餘空間作爲fr分配空間,所有fr相加之和作爲分母,分子爲每個元素對應的fr的值。(當然,在Grid語境下,我們是不需要設置width屬性的,這樣說是爲了讓大家容易理解)。

這樣說來好像fr僅僅是%的另外一種寫法,隨着我的介紹,你就會發現fr優於%的地方。

Grid屬性腦圖.png
接下來,我還是以腦圖爲思路介紹Grid的各個屬性。

容器屬性

grid-template

grid-tempalte是三個屬性的簡寫,這三個屬性都是描述整塊區域即多個網格單元的屬性。

grid-template-rows

grid-template-rows是描述橫向單元軌道屬性的。可以試想一下,我們在一個Grid容器中,關於這個屬性,我們關心的是什麼呢?無非就是這個容器中有多少行,每行的高度;所以,這個屬性就是讓我們定義這些值得。先來看語法:
grid-template-rows: <track-size> | <line-name> <track-size> ...;
這個屬性除了可以定義軌道尺寸和個數之外,還允許我們定義兩個軌道之間的網格線的名稱,至於他的作用,我們先按下不表,先來看這個屬性是怎樣定義每個軌道的尺寸和軌道個數的。
先來看一個例子:

.container{
    grid-template-rows:200px auto 1fr 1fr 20%;
    grid-row-gap:10px;//定義軌道之間的間距
}

grid-template-rows定義了五個值,表示Grid容器裏面有五行,可以使用任意的定義尺寸的方式,效果如下

clipboard.png

其中auto值就表示元素的實際佔用大小。
Grid分配空間首先計算除了fr對應軌道的尺寸,然後將剩餘尺寸按照比例分配給fr加持的元素。
以上,我們在Grid容器裏定義了五行容器軌道,當我們定義軌道過多時,可以使用repeat函數來減少我們的工作量,語法爲:
grid-template-rows:repeat(n, size)
例子:

.container{
    grid-template-rows:repeat(5,1fr);
    grid-row-gap:10px;
}

clipboard.png
上面就定義了五個網格軌道,每個軌道的高度是Grid容器高度的1/5。

grid-template-columns

grid-template-columns 與 grid-template-rows使用方法是一致的,這兩個屬性共同作用於Grid容器,相當於把Grid容器分割爲m*n個子區域。
例子:

.container{
    grid-template-rows:repeat(3,1fr);
    grid-template-columns: repeat(2, 1fr)

上面這個例子就會得到六個均等分的子區域。

通過上面兩個屬性,相信大家對Grid佈局有一個基本的認識了,想必對Grid二維佈局的模式也有一些概念了,接下來纔是Grid精彩之處,震撼靈魂的地方!

grid-template-areas

上面兩個屬性分別設置Grid行屬性和列屬性,grid-template-areas是設置Grid區域的。所謂區域是由一個或多個行、列、單元組成的一篇區域。首先看一下語法:

.container {
  grid-template-areas: 
    "<grid-area-name> | . | none | ..."
    "...";
}

其中
grid-area-name表示網格區域的名稱
.表示空的網格區域
none 表示沒有定義網格區域
在我們平時開發時,經常會出現上頭下尾中間兩欄佈局的情況,下面我們使用grid-template-areas完成這樣的佈局。

<style>
.container{
  display:grid;
  grid-template-rows:60px auto 60px;
  grid-template-columns:100px 1fr;
  grid-template-areas:
    "header header"
    "left right"
    "footer footer";
}

.container .item:first-child{
  grid-area: header;
}

.container .item:nth-child(2){
  grid-area: left;
}

.container .item:nth-child(3){
  grid-area: right;
}

.container .item:nth-child(4){
  grid-area: footer;
}
</style>
.container{
  display:grid;
  grid-template-rows:60px 1fr 60px;
  grid-template-columns:100px 1fr;
  grid-template-areas:
    "header header"
    "left right"
    "footer footer";
}

.container .item:first-child{
  grid-area: header;
}

.container .item:nth-child(2){
  grid-area: left;
}

.container .item:nth-child(3){
  grid-area: right;
}

.container .item:nth-child(4){
  grid-area: footer;
}
.container{
  display:grid;
  grid-template-rows:60px 1fr 60px;
  grid-template-columns:100px 1fr;
  grid-template-areas:
    "header header"
    "left right"
    "footer footer";
}

.container .item:first-child{
  grid-area: header;
}

.container .item:nth-child(2){
  grid-area: left;
}

.container .item:nth-child(3){
  grid-area: right;
}

.container .item:nth-child(4){
  grid-area: footer;
}
</style>

<div class="container">
    <div class="item">1</div>
    <div class="item">2</div>
    <div class="item">3</div>
    <div class="item">4</div>
</div>

在Grid容器中,我們定義了6個網格單元,使用grid-tempalte-areas劃分了header footer left right 四片區域;而在grid元素中,每個元素使用grid-area來指定元素所對應的grid區域。因此,我們雖然劃分了6個單元,但可以使用四個元素來表示。

clipboard.png

是不是很神奇呢,更神奇的是,grid-area-name是支持中文定義的。

以上grid-template的子屬性就說完了,grid-template是以上那三個屬性的簡寫方式,語法如下:

grid-tempalte:<'grid-template-rows'> / <'grid-template-columns'>`
`grid-tempalte:[ <line-names>? <string> <track-size>? <line-names>? ]+
               [ / <explicit-track-list> ]?

例如上面的例子可以這樣簡寫

grid-template: 
    "header header" 60px
    "left right" 1fr
    "footer footer" 60px
    / 50px 1fr;

grid-gap

grid-gap用來描述Grid 區域之間間隙的尺寸大小。語法如下:

.container {
    grid-gap: <grid-row-gap> <grid-column-gap>;
}

grid-gap是簡寫屬性,也可以分別定義grid行間隔和grid 列間隔。

.container{
    grid-row-gap:10px;
    grid-column-gap:10px;
}

grid-gap與margin與padding不同,它不佔用當前元素的盒模型的位置。

clipboard.png
上圖顯示的很清楚,3號元素的margin 與 padding 均爲零。

place-items

place-items justify-itemsalign-items的簡寫方式
這兩個屬性分別定義了Grid元素水平與垂直分佈方式。
語法如下:

    justify-items: stretch | start | end | center;
    align-items: stretch | start | end | center;

對於這四個屬性,默認stretch,相信讀者在熟悉了Flex佈局後都不會陌生,這裏不多做解釋,直接看例子,以align-items 爲例:

.container{
  display:grid;
  grid-template: 
    "header header" 160px
    "footer footer" 160px
    / 160px 160px;
  
  height:500px;
  grid-row-gap:10px;
  grid-column-gap: 10px;
}

首先定義四個grid單元,每個單元的長寬均爲160px
接下來我們更改align-items的值

align-items:stretch
clipboard.png

align-items:center

clipboard.png

align-items:start

clipboard.png

align-items: end

clipboard.png
爲了方便大家理解,我用紅框框出每個Grid單元所佔用的空間。由此可以看出,place-items屬性是用來表明一個元素在當前grid單元中的分佈方式,這個元素的拉伸,居中等都是以grid單元作爲參考的。

place-content

place-content同樣是一個簡寫屬性,它包括:justify-content 和 align-content,它表示grid元素在grid容器中的分佈方式,只有當grid容器中有剩餘空間的時候才起作用。
語法如下:

justify-content: stretch | start | end | center | space-between | space-around | space-evenly;
align-content: stretch | start | end | center | space-between | space-around | space-evenly;

屬性值得含義同Flex;這裏不再過多說明,讀者可以自行驗證。

grid-auto-rows 與 grid-auto-columns

grid-auto-rows 和 grid-auto-columns;用於當實際的Grid的元素多餘劃分的Grid元素時,定義多餘Grid元素的長寬;
例如我們在HTML裏面一定了五個Grid元素,但是在css中只定義了2*2的Grid單元,可以使用grid-auto來定義多出來的軌道的尺寸。

grid-auto-flow

grid-auto-flow的用法需要結合下面的元素屬性來說明。

元素屬性

grid-column-start grid-column-end

兩個屬性是用來定義Grid元素列方向上的起始與終止位置。
語法格式爲:

    grid-column-start: <number> | <name> | span <number> | span <name> | auto

其中:
number爲起止第幾條網格線
name 爲網格線的名稱
span <number>網格元素會跨越網格單元的數量
span <name> 當前的網格元素會在哪一個網格線上開始or終止

注意 使用span 如果是start,表示這個從開始的位置跨過的grid單元,如果是end 表示這個元素覆蓋的grid單元。
grid-column是它們的簡寫方式,語法爲:

grid-column:grid-column-start / grid-column-end

grid-row-start grid-row-end

grid-row屬性與grid-column用法一致,這裏不過多贅言,直接看例子:


.container{
  display:grid;
  grid-template-rows:[rone]1fr[rtwo]3fr[rthree]1fr[rfour];
  grid-template-columns: [cone]1fr[ctwo]5fr[cthree]2fr[cfour];
  height:500px;
}

.item:first-child{
  grid-column-start:1;
  grid-column-end:cfour;
  grid-row-start:rone;
  grid-row-end: 2;
  
}

.item:nth-child(2){
  
  grid-column-start:1;
  grid-column-end:span 1;
  grid-row-start:rtwo;
  grid-row-end: span cthree;
}

.item:nth-child(3){
  
  grid-column-start: ctwo;
  grid-column-end:4;
  grid-row-start:rtwo;
  grid-row-end: span cthree;
} 



.item:nth-child(4){
  grid-column-start:1;
  grid-column-end:4;
  grid-row-start:3;
  grid-row-end: span 4;
} 

效果:

clipboard.png
首先在Grid容器中劃分出9個grid單元,這九個單元被六個網格線所分割,並給這六個網格線命名。在四個Grid元素中定義橫行的起始位置。
grid-row 與 grid-column結合使用,可以起到與Grid-template-areas同樣的效果。

grid-area

grid-area我們在前面已經接觸過一部分了,他與Grid容器中的grid-template-areas一起定義,也是grid-column與grid-row的簡寫屬性,語法爲:

 grid-area: <name> | <row-start> / <column-start> / <row-end> / <column-end>;

name爲grid區域的名稱,與grid-tempalte-areas結合使用。

上面我們的例子就可以用grid-area表示:

.item:first-child{
    grid-area:1/rone/2/cfour;
}

place-self

justify-self

justify-self定義Grid元素的水平佈局方式的,例如,我們在Grid容器中定義justify-items的屬性爲默認屬性,而在某一個Grid元素中定義justify-self爲center,那麼其他元素表現爲拉伸,這個元素則單獨表現爲居中。也就是說,justify-self在Grid容器中對應的屬性是justify-items。
語法爲:

.item {
    justify-self: stretch | start | end | center;
}

algin-self

align-self與justify-self一致,改變的是這個元素的垂直部署方式,與容器中align-items對應,
語法爲:

.container {
    align-self: stretch | start | end | center;
}

由於這四個屬性值已經在我們的系列文章中出現多次,這裏不再多說。

plac-self是以上兩個屬性的簡寫方式,語法爲:

.container {
    place-items: <align-self> / <justify-self>;
}

再論fr

Grid的所有屬性已經介紹完畢了,在對Grid宇宙有了一個基本的認識後,我們再回頭看一下Grid宇宙中出現的新尺寸單位——fr。
可能大家在剛剛結仇到fr這個單位時,都會認爲它是%的一個別名;但是,我們來看最下面的例子:
我們在Grid容器中劃分出四個Grid區域,並定義每個區域的寬度爲25%,並定義每個Grid元素之間的gap寬70px;

.container{
  display:grid;
  grid-template-columns: repeat(4, 25%);
  grid-column-gap: 70px;
}

效果如下:
clipboard.png
很明顯,這裏元素溢出了。這種情況是我們不想看到的。
下面,我們將25%替換爲1fr看一下效果:

clipboard.png
效果對比很明顯。
而造成兩者顯著區別的原因是二者的計算空間方式的不同。使用百分比它的分母是父元素的width或者height,而fr的分母是父元素中剩餘空間的尺寸;css會首先計算使用%和px定義的元素尺寸,剩下的空間再由fr元素進行比例分配。這就是使用fr不會出現元素溢出的情況。當然我們也可以使用calc避免溢出的尷尬,但是兩種解決方案孰優孰略已經一目瞭然了。

Flex 與 Grid

Flex佈局與Grid佈局有很多相似的地方,例如justify-content和justify-items的用法。但是更多的是不同,最重要的是Grid開拓了二維佈局的方式,相比於傳統的css佈局方式(Flex、bootstrp 12列),Grid開創了網格的概念,用戶可以從橫縱兩個方面部署元素。正是因爲如此,在Grid宇宙中,傳統的css佈局、尺寸屬性就顯得格格不入了。而Grid佈局的二維特性所帶來的整體觀,使Grid在頁面級別樣式上更加遊刃有餘。而Flex相比於Grid 更加適合小組件上的樣式開發,二者並不衝突相信在Grid 與 Flex雙雙加持之下,一定會收穫更好的開發效果!
Grid佈局還是一個較新潮的概念,我也是一般看資料學習,一邊分享,由於缺少實際的開發經驗,對於很多屬性的應用場景還沒有很深入的理解,故而有的屬性一筆帶過;如果我有理解不正確的地方,歡迎大家指正!

參考文獻

MDN:Grid Layout
張鑫旭:寫給自己看的display: grid佈局教程
知乎:CSS 新的長度單位 fr 你知道麼?

What's more:

JavaScript五十問——對比來說CSS的Grid與FlexBox(上篇)

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