前言
写作本文起源于知乎的一个问题:【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 属性的特殊性也决定了不可能也不应该只是单纯的叠加组合类名。