未來佈局之星Grid

網易考拉前端團隊發表於2017-09-24

grid是一個趨勢

Grid-layout不是為了取代flex-layout,它是flex的補充。grid擅長二維佈局,flex擅長一維佈局。他們需要各司其職。

千呼萬喚始出來的grid-layout終於在2017年3月開始支援得到了部分瀏覽器的支援。


flex並不能滿足我們對於頁面整體佈局的需求,Don't use flexbox for overall page layout這篇文章闡述了這麼一個觀點:不要用彈性盒子佈局整體的頁面。而我們在平常使用中也能感受到flex在做整體頁面佈局的時候,還是有不完善的地方,還是需要大量的inline-box、float來排布內容(// TODO 售後單詳情頁作為例子);另外一個問題我們後面瞭解了grid之後會再說

grid = table2.0?

二維佈局方式,可以按照行列方式排列內容。把頁面劃分為幾塊,指定不同內容區塊的大小、位置和層級。

有觀點說grid很像table2.0,他們是有相同之處的。比如都是把元素排列成行和列。但是表格和grid的區別在於,表格是有內容結構的,不能很自由地在裡面做佈局。而grid內部元素可以自由設定位置,允許重疊和設定層級的樣。

幾個術語

網格容器 grid-container

網格容器為其內容建立新的網格格式化上下文,是內部網格項的邊界。

網格線 grid-line

水平垂直分割線,構建出網格軌道、網格單元格和網格區域。就像經緯,分割出南北半球、東西半球,熱帶、南北溫帶、南北寒帶。網格線是有數字索引的,也可以自己取名字。經緯線都是有數字的,也可以命名,比如本初子午線、赤道。

網格軌道 grid-track

網格內容塊之間的水平或垂直的空間。濱盛、濱和、濱興、濱安、濱康,江陵、江暉、江漢、江虹。(郊區的經典命名,攤手)

網格單元格 grid-cell

網格內容的單位區塊,是可以放置內容的最小區塊。比如用橫縱三條網格線劃分了頁面,那麼單元格就是九宮格中的一塊

網格區域 grid-area

以網格線為界劃定一塊區域。原本網易和阿里巴巴都是佔用一個單元格,現在都要擴建了,佔用兩個,兩個加起來就是它們各自的網格區域。

兩個例子

在瞭解具體的屬性之前,來一個最簡單的例子

這樣看會覺得grid還是很有趣的,先分塊,然後指定每一塊的區域範圍。塊是可以重疊的。但是這只是開始,grid為我們帶來了17個新特性,在瞭解屬性之前再來看一個例子,經典佈局方案 —— 聖盃佈局 holy grail layout。

  • 網頁常見佈局:頁首、頁尾、主要內容區塊、兩邊都有一個側邊列。
  • 兩邊帶有固定寬度,中間寬度可變
  • 中間三列內容等高
  • 頁尾總處於視窗的底部,即使內容沒有填滿整個視窗的高度
  • 響應式佈局,在較小的視窗中所有模組都100%寬度顯示

大家可以迅速在腦子裡碼一下這個介面寫個樣式,不至於說複雜 但總之不簡單吧。我們看看grid是怎麼做的

// key code
.hg__header { grid-area: header;}
.hg__footer { grid-area: footer;}
.hg__main { grid-area: main;}
.hg__left { grid-area: navigation;}
.hg__right { grid-area: ads;}

.hg {
    display: grid;
    grid-template-areas:    "header header header"
                            "navigation main ads"
                            "footer footer footer";
    grid-template-columns: 250px 1fr 300px;
    grid-template-rows: 100px 
                        1fr
                        80px;
    min-height: 100vh;
}複製程式碼

步驟:

  1. 定義網格

    1. 設定網格區域別名,grid-area: ,指定塊所在區域的時候方便引用。前一個簡單的例子,grid-area是用來指定網格區域上下左右的網格線各是什麼,所以grid-area既可以指定網格區域的大小和位置,還可以設定區域的別名。
       grid-area: main;
           grid-row-start: main;
           grid-column-start: main;
           grid-row-end: main;
           grid-column-end: main;複製程式碼
    2. grid-template-areas: "- - -" "- - -" "- - -";可以非常直觀指定網格的佈局。它的值是空格分隔的字串陣列,每一個字串代表一行。每一個字串中是空格分隔的網格單元格列表,一個網格區域要跨幾列就寫幾次。比如例子中header、footer寫了三次,它們都是跨整個區域寬度的。
  2. 設定單元格的高度和寬度

    有一個css3的新單位,fr,在一串數值中出現的話表示根據比例分配某個方向上的剩餘空間。設定行高的時候分行寫是為了清晰一點。

  3. 設定固定位置的頁尾

  4. 響應式佈局,使用媒體查詢。重置佈局和行高

grid-container

1. grid-template-columns | grid-template-rows

grid-template-columns: <track list>定義網格的行數、列數、網格大小

有很多中形式,常見的是這麼幾種:

grid-template-columns: 100px 1fr;
grid-template-columns: [linename] 100px;    // 定義網格線名字
grid-template-columns: [linename1] 100px [linename2 linename3]; // 一條網格線多個名字
grid-template-columns: minmax(100px, 1fr);  // 最小100px, 最大滿屏
grid-template-columns: fit-content(40%);    // 指定最大值不超過屏寬40%
grid-template-columns: repeat(3, 200px);    // 三列200px複製程式碼
// 給網格線指定名字
.box {
    grid-template-columns: [first] 40px [line2] 50px [line3] auto [col4-start] 50px [five] 40px [end];
    grid-template-rows: [row1-start] 25% [row1-end] 100px [third-line] auto [last-line];
}複製程式碼

2. grid-template-areas

定義網格區域,使用grid-area呼叫宣告好的網格區域名稱放置對應的網格專案。定義一個顯式的網格區域。

3. grid-row-gap、grid-column-gap

定義網格之間的間距(不包括grid-item到容器邊緣的間距)

4. justify-items: start | end | center | stretch(預設);

定義網格子項的內容水平方向上的對齊方式,類似於flex-container的justify-content,只不過沒有space-around和space-between

1bc096d4ee73927e.png
1bc096d4ee73927e.png

35fecd4fbf754a84.png
35fecd4fbf754a84.png

9a2b4b82c9b6a65b.png
9a2b4b82c9b6a65b.png

85e7fb64351898e4.png
85e7fb64351898e4.png

5. align-items: start | end | center | stretch(預設);

定義網格子項的內容垂直方向上的對齊方式,類似於flex-container的align-items

7a410b6d17acd72e.png
7a410b6d17acd72e.png

20f8f733235797ea.png
20f8f733235797ea.png

9e1f32b77d573d04.png
9e1f32b77d573d04.png
85e7fb64351898e4.png
85e7fb64351898e4.png

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

當出現網格容器容量大於網格總大小,比如每一個網格子項都用了固定值的時候,指定網格在網格容器中和縱軸的對齊方式。後面三個屬性值的區別在:

  1. space-around: 始末兩端的間距是網格間距的一半
  2. space-between: 始末兩端的間距為零
  3. space-evenly: 始末兩端的間距與網格間距相等




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

和justify-content對齊方向垂直,指定網格和橫軸的對齊方式。

e14e12bdfbf2e3e4.png
e14e12bdfbf2e3e4.png

8006fb09977ce5c8.png
8006fb09977ce5c8.png

61ffebd9e0da3f0b.png
61ffebd9e0da3f0b.png
28e49f66a9c4455a.png
28e49f66a9c4455a.png

b9bfc04642c818d7.png
b9bfc04642c818d7.png

f1845a4b578d8191.png
f1845a4b578d8191.png

f06633de75642a36.png
f06633de75642a36.png

8. grid-auto-columns、grid-auto-rows; grid-auto-flow

grid-auto-columns | grid-auto-rows作用是網格單元格不夠的時候建立隱式的網格放置grid-item。看一個例子

我們只定義了一個1×1的網格容器,box1放了進來,然後其他的三個怎麼辦呢?漏出來。box2接在box1後面渲染至螢幕右側,box3和box4在底下渲染,高度僅僅為內容高度。

指定了grid-auto-columns: 200px; grid-auto-rows: 200px;,相當於在容器中橫縱都建立了更多的隱式的200*200的網格單元來盛放可能多出來的元素。

與之相關的還有另一個屬性:grid-auto-flow,在我們沒有設定這個屬性的時候,多餘的元素也按照從左到右從上到下的順序排列,這個屬性是控制自動佈局演算法的。

grid-auto-flow: row | column | row dense | column dense;

  1. row為預設值,代表自動佈局演算法在每一行中依次填充,只有必要時才會新增新行。
  2. column代表自動佈局演算法在每一列中依次填充,只有必要時才會新增新行。
  3. dense代表告訴自動佈局演算法如果更小的子項出現時嘗試在網格中填補漏洞。(不建議使用,可能會使佈局產生混亂)

grid-item

1. grid-column-start | grid-column-end | grid-row-start | grid-row-end

grid-row: grid-row-start / grid-row-end
grid-column: grid-column-start / grid-column-end | grid-column-start | span <value>複製程式碼

f0988aebf7ce8786.png
f0988aebf7ce8786.png

如果沒有顯式設定grid-column-end/grid-row-end,網格子項將預設跨越一個網格單元。此外,網格子項可以互相重疊,可以使用z-index來控制他們的層疊順序。

有一些元素,我們想讓它貫穿整個視口,比如像 header、footer,對於小螢幕,兩列布局:

.header, .footer {
  grid-column: 1 / 3;
}複製程式碼

不幸的是,當我們換到大屏的時候,一行12列,這些元素將僅僅佔滿前兩列,並不會佔滿12列,我們需要定義新的grid-column-end,並且把他的值設為 13. 這種方式比較麻煩,還有一種簡單的方式,grid-column: 1 / -1;,這樣不論在什麼螢幕尺寸下,它們都是佔滿整行的了。就像下面這樣:

.header, .footer {
  grid-column: 1 / -1;
}複製程式碼

2. grid-area

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

3. justify-self: start | end | center | stretch 網格單元格內容水平方向上的對齊方式 。與flex中的justify-self。

c096a95f6300d932.png
c096a95f6300d932.png

bb01f2f4ea312d78.png
bb01f2f4ea312d78.png

3ca2ef564834e834.png
3ca2ef564834e834.png

caddebe3320088bf.png
caddebe3320088bf.png

4. align-self: start | end | center | stretch 網格單元格內容垂直方向上的對齊方式 。類似與flex中的align-self。

1f1b1806e925fe2b.png
1f1b1806e925fe2b.png

grid-align-self-end.png
grid-align-self-end.png

0ad87bbf53ecc6a3.png
0ad87bbf53ecc6a3.png

caddebe3320088bf.png
caddebe3320088bf.png

grid-layout帶來的函式

1. repeat()

repeat()提供了一個緊湊的宣告方式。如果行列太多並且是規則的分佈,我們可以用函式來做網格線的排布。

grid-template-columns: repeat(3, 20px [col-start]) 5%;
// 等價於
grid-template-columns: 20px [col-start] 20px [col-start] 20px [col-start] 5%;
}複製程式碼

2. minmax()

minmax()相當於為網格線間隔指定一個最小到最大的區間。如果min>max,這個區間就失效了,展示的是min。

3. fit-content()

fit-content()相當於 min('max-content', max('auto', argument));

flex佈局的另一個問題

flex-layout的佈局另外一個問題是,在有大量內容繪製到頁面上、或者內容更改的情況下,在2g或者網路載入不穩定的時候,頁面是不穩定的。Flexbox vs Grid page layout。內容以流式從服務端獲取使用者可以在頁面全部載入出來之前就看到內容,但是在flex佈局下會導致佈局的重排。正是因為flex本身就是一個彈性的佈局。但grid也不是完全可以避免佈局重排的問題——有前提:

必須讓你的網格劃分是可以預先確定的,比如是根據視窗寬高確定的。如果是根據內容而定,那麼也是會崩壞的。複製程式碼

下面的例子中,網格的列寬根據內容而定,因此也會根據內容而變。後面的aside並沒有定義在網格容器當中,是一個動態建立的元素。一旦被建立就會導致頁面重繪

.container {
    display: grid;
    grid-template-columns:
    (foo)   max-content,
    (bar)   min-content,
    (hello) auto;
}

aside {
    grid-column: 4;
}複製程式碼

但是也不要放棄flex-layout,它是目前為止最厲害的頁面佈局屬性,是時代召喚的結果,只是它並不適合佈局整個頁面框架。flex在響應式佈局中是很關鍵的,它是內容驅動型的佈局。不需要預先知道會有什麼內容,可以設定元素如何分配剩餘的空間以及在空間不足的時候如何表現。顯得較為強大的是一維佈局的能力,而grid優勢在於二維佈局。這也是他們設計的初衷。

大概可以設想,網格佈局被廣泛支援之後會出現很多網格佈局內嵌flex的佈局情形。

subgrid暫時還沒有瀏覽器支援,規範也是在改動之中的,所以先不介紹了。

參考

Don't use flexbox for overall page layout

(譯)原生CSS網格佈局學習筆記

網格佈局(CSS Grid Layout)淺談

CSS Grid Layout

擁抱未來的CSS佈局方式:flex與grid佈局

A Complete Guide to CSS Grid Layout

相關文章