前言
寫作本文起源於知乎的一個問題:【CSS Grid 佈局那麼好,為什麼至今沒有人開發出基於 Grid 佈局的前端框架呢?】
這篇文章拖沓了兩個月,是因為真的不知道從哪裡說好。這個問題的所有回答幾乎都沒有切中問題的本質,而且對 CSS Grid 也有很深的誤解。另外這個問題的描述可能不太恰當,因為基於 CSS Grid 的框架已經有了。感興趣的朋友可以瞭解一下 Flex-Layout,它是一個基於指令佈局的神器。大家不要被專案名誤導,雖然叫 flex-layout
,其實有 flex
和 grid
兩種指令。
我覺得這個問題更恰當的描述應該是【為什麼沒有基於 Grid 佈局的純 CSS 前端框架呢】,基於 Float 和 Flex 的 CSS 佈局框架多如牛毛,但是一直沒看到基於 CSS Grid 的實現。我在寫 Snack 的時候也曾考慮過在 v3 版本中整合 CSS Grid,但是後來發現有些不現實,下文詳述。據說 Bootstrap 也曾打算在 v5 版本整合 CSS Grid,但是目前來看依然沿用了 Flex 佈局。我個人覺得基於指令實現的 flex-layout
應該是整合 CSS Grid 的最佳方式。
本文打算從相反的角度聊一聊,假如要實現一個基於 CSS Grid 的佈局框架,我們會遇到哪些問題呢?
瀏覽器相容性
先說一下相容性,很多人總覺得 CSS Grid 的相容性不行。其實不然,下圖的統計資料能夠看出大部分瀏覽器對 CSS Grid 的支援還是不錯的,要知道 Flex 在 IE10 上面可以使用的屬性也非常有限。據統計目前 CSS Grid 使用率和 Flex 已經基本持平了。所以相容性並不能回答開篇提出的問題。
CSS 佈局框架的特點
CSS 佈局主要有四種,上古時期用的是 table,這個就不說了,我們只討論新世紀常用的 Float、Flex、Grid。這三種佈局方式就是遞進的關係,逐步加強了 CSS 佈局的便利性。CSS 佈局的最佳實踐當屬 Bootstrap,其中 Float 和 Flex 佈局方案都已經被 Bootstrap 實現,唯獨 Grid 方案遲遲不見蹤影。
CSS 佈局框架的關鍵在於如何設計柵格,我們先看看在 Bootstrap 中是怎麼進行佈局的。
<div class="row">
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
</div>
<div class="row">
<div class="col-md-8">.col-md-8</div>
<div class="col-md-4">.col-md-4</div>
</div>
<div class="row">
<div class="col-md-4">.col-md-4</div>
<div class="col-md-4">.col-md-4</div>
<div class="col-md-4">.col-md-4</div>
</div>
<div class="row">
<div class="col-md-6">.col-md-6</div>
<div class="col-md-6">.col-md-6</div>
</div>
用過 Bootstrap 的人對以上結構一定不陌生。在 Bootstrap v4 中,由於 Flex 佈局功能的增強,row
也可以搭配 utility
使用。
<div class="row align-items-start">
<div class="col">One of three columns</div>
<div class="col">One of three columns</div>
<div class="col">One of three columns</div>
</div>
<div class="row align-items-center">
<div class="col">One of three columns</div>
<div class="col">One of three columns</div>
<div class="col">One of three columns</div>
</div>
<div class="row align-items-end">
<div class="col">One of three columns</div>
<div class="col">One of three columns</div>
<div class="col">One of three columns</div>
</div>
row
和 col
的組合幾乎已經成為柵格佈局的共識和標準。接下來我們就用這種設計方式手撕一個 Grid 柵格看看效果。
手撕 CSS 佈局柵格
其實手撕一個 CSS 佈局柵格並不難,關鍵是要熟悉前處理器的迴圈功能。下面我們分別用 Float、Flex 及 Grid 實現一個簡化版的柵格佈局並對比一下它們之間的差異。
Float
Float 是過去實現流體佈局的唯一方式,難點在於處理父元素的塌陷,這算是一個老生常談的問題了,本文不再贅述。以下是一個簡易版的 Float 柵格:
See the Pen float-columns by Zongbin (@nzbin) on CodePen.
這種柵格簡單實用,堪稱經典。但是 Float 佈局的侷限性很大,如果柵格的高度不相同(比如將第一個柵格的高度加高),佈局就會出現塌陷。
Flex
Flex 的誕生幾乎解決了 Float 柵格的所有痛點,比如上面提到的塌陷問題。以下是一個簡易版的 Flex 柵格:
See the Pen flex-columns by Zongbin (@nzbin) on CodePen.
其實在一維佈局中,Flex 已經足夠好用,至少在流體佈局中沒發現需要改進的地方,所以我們是否還有必要引入二維佈局方案?
Grid
重點來了!重點來了!重點來了!
Grid 是比 Flex 更高階的佈局技術,所以單純替換 Flex 實現一套柵格佈局系統完全沒有問題。以下是一個極其簡易的 Grid 柵格,幾乎和 Flex 柵格的效果沒有區別。
See the Pen grid-columns by Zongbin (@nzbin) on CodePen.
Grid 除了能實現 Flex 的佈局之外,還可以在縱向空間對每一行進行調整(比如設定 grid-template-rows: 1fr 2fr
)。另外,Grid 設定其它列數的柵格也比較容易,比如設定 10 列柵格,只需要設定 grid-template-columns: repeat(10, 1fr)
即可;如果是 Flex,則需要重新計算柵格的寬度。
上面示例中展示的 CSS Grid 的功能只是冰山一角。作為一個二維佈局技術,它的屬性寫法非常繁雜,比如另外一個強大的功能 grid-template-areas
MDN Demos。這種屬性定義方式很難使用類名的疊加組合實現對應的功能。
如果只是簡單地將 Flex 柵格替換成 Grid 柵格,我個人感覺意義不大,一個基於 CSS Grid 全新的柵格系統應該儘可能的發揮它所有的優勢。這或許就是 CSS Grid 柵格系統遲遲沒有出現的原因吧。我不知道文章寫到這裡是否清楚的表達了我的觀點,或者說能否解答大家對於開篇問題的疑惑。接下來再看看 flex-layout
中的 Grid。
Flex-Layout 中的 Grid
因為 flex-layout
是基於指令的實現,所以不像類名疊加的方式那樣有很大的侷限性,幾乎可以發揮 CSS Grid 的全部優勢。以下就是 grid-template-areas
的定義方法。
<div gdAreas.xs="header | sidebar | content | sidebar2 | footer" gdGap="1em"
gdColumns.xs="none"
gdAreas.sm="header header | sidebar content | sidebar2 sidebar2 | footer footer"
gdColumns.sm="20%!"
gdAreas.gt-sm="header header header | sidebar content sidebar2 | footer footer footer"
gdColumns.gt-sm="120px auto 120px" gdGap.gt-sm="20px" [ngStyle]="{'max-width': 'auto'}"
[ngStyle.gt-sm]="{'max-width': '600px'}">
<div class="blocks one" gdArea="header">Header</div>
<div class="blocks two" gdArea="sidebar">Sidebar</div>
<div class="blocks three" gdArea="sidebar2">Sidebar 2</div>
<div class="blocks four" gdArea="content">Content
<br /> More content than we had before so this column is now quite tall.</div>
<div class="blocks five" gdArea="footer">Footer</div>
</div>
flex-layout
無論是設計思路還是實現方式都讓我頂禮膜拜,它與傳統的 Bootstrap 形式的柵格有著本質區別。本文的重點不是介紹 flex-layout
,如果大家感興趣,還是看官網介紹吧 Flex-Layout。
總結
醞釀了兩個月個文章,感覺還是沒有把自己的觀點表達清楚。回到問題本身,【為什麼沒有基於 Grid 佈局的純 CSS 前端框架呢】,個人覺得最主要的還是傳統寫法無法發揮 CSS Grid 的全部優勢,至少用以往的類疊加方式是無法做到的。CSS Grid 屬性的特殊性也決定了不可能也不應該只是單純的疊加類名。