震惊!很多人都不知道 CSS Grid 框架早就有了!

前言

写作本文起源于知乎的一个问题:【CSS Grid 布局那么好,为什么至今没有人开发出基于 Grid 布局的前端框架呢?

这篇文章拖沓了两个月,是因为真的不知道从哪里说好。这个问题的所有回答几乎都没有切中问题的本质,而且对 CSS Grid 也有很深的误解。另外这个问题的描述可能不太恰当,因为基于 CSS Grid 的框架已经有了。感兴趣的朋友可以了解一下 Flex-Layout,它是一个基于指令布局的神器。大家不要被项目名误导,虽然叫 flex-layout,其实有 flexgrid 两种指令。

我觉得这个问题更恰当的描述应该是【为什么没有基于 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>

rowcol 的组合几乎已经成为栅格布局的共识和标准。接下来我们就用这种设计方式手撕一个 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 属性的特殊性也决定了不可能也不应该只是单纯的叠加类名。