概述

一般来说设计工具中都有网格、参考线等工具来辅助一些对齐,排布功能,如 photoshop 和 sketch 等。

在没有 CSS 对应的网格功能之前,一般会使用 float 或 flexbox 等技术来模拟这种网格系统,比如960sbootstrap 的网格系统。虽然模拟出来的网格系统在一定程度上可以一些解决问题,但是在面临一些如 Win10 UI 这种还是有点力所不及,如下图的布局:

image.png

既然有了使用场景的需求,肯定就会有新的标准技术出现,这就是这里要说的 CSS Grid Layout。其主要有以下优势:

  1. 为布局而生的二维网格布局系统。
  2. 功能强大。Win10 UI、响应式布局改变、二维列表等都是它的菜。
  3. 对比 flexbox 来说:flexbox 为单维、内容优先;而 grid 为二维,布局优先。如下图,图一为 flexbox,图二为 grid

image.png

PS:虽然 flexbox 也可以折行,视觉上看起来形成的也是二维的,但是它并不能进行跨行跨列的操作,所以 flexbox 并不是二维的布局。

同 flexbox 一样,grid 布局也是由两层结构组成,父元素设置 display: grid 形成 grid container,直接子元素就此成为 grid items,然后就可以通过给父子元素设置一些 CSS 属性,轻松实现一些网格布局了。

基本术语

既然是网格系统,那肯定是由线条组成的一个个格子,如下图水平红线与垂直蓝色相交就形成一个 2*3 的网格。

image.png

轴线(axis)

flexbox 有两个轴,分别是 main axis(主轴) 和 cross axis(十字轴)。而 grid 作为二维的布局系统,也有对应的两个轴,分别为:inline axis(行内轴,也叫 row axis)和 block axis (块轴,也叫 column axis)。理解这两个轴非常重要,因为分布对齐属性就是根据这两个轴来进行的。如下图:

image.png

注意:flexbox 的两个轴是会根据排列方向而改变的,但是 grid 的两个轴是不变的。

Grid container & grid items

给一个元素设置 display: grid 后,该元素就是 grid container 了,同时其直接子元素就是 grid items。

  1. <div class="grid-container">
  2. <div class="grid-item"></div>
  3. <div class="grid-item"></div>
  4. <div class="grid-item"></div>
  5. <div class="grid-item"></div>
  6. </div>
/* grid container */
.grid-container {
  display: grid;
}

/* grid items */
.grid-item {}

Grid lines

构成网格系统的线条,就叫 grid lines,如下图:

image.png

Grid cell

而线条所围成的每一个格子,就叫 grid cell,如下图:

image.png

Grid track

整个一行或者一列,就叫 grid track(对应 table 的 row 或 column),如下图:

image.png

Grid area

由任意两条横向网格线和两条纵向网格线所组成的区域都是 grid area(可以对其命名,以方便使用),其可能由一个或多个 grid cell 组成,如下图:

image.png

Grid cell vs grid items

最后考虑到有些格子会合并,所以 grid items 可能是单个的 grid cell,也有可能是多个 cell 合并的结果,如下图 3 和 4 item 就是 cell 合并的结果:

image.png

PS:其实网格系统跟我们日常工作中的 excel 有点像,如下图:

image.png

牛刀小试

因为网格系统的属性特别多,功能也非常的强大,所以这里先直接选个实战来简单熟悉下(具体深入下一节再详细分析)。下面开始实现概述中的效果图:

image.png

第一步:搭建结构

先数出 items 的个数,这里为 11 个,如下图:

image.png

设计 HTML 结构如下:

<div class="grid">
    <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 class="item">6</div>
    <div class="item">7</div>
    <div class="item">8</div>
    <div class="item">9</div>
    <div class="item">10</div>
    <div class="item">11</div>
</div

第二步:设计网格

写好结构后,再根据要实现的效果图拆分格子。如下图,红色和灰色的线条就是 grid lines:

image.png

这样我们就得到一个 3 6 的网格,其中 grid cell 的大小为 140px 140px,间距为 20px。现在我们就可以使用 grid container 的 CSS 相关属性完成初步的网格布局,代码如下:

.grid {
  display: grid; /* 定义网格布局 */

  /* 定义3行6列 */
  grid-template-rows: 140px 140px 140px; /* 每个值表示每行的高度 */
  grid-template-columns: 140px 140px 140px 140px 140px 140px; /* 每个值表示每列的宽度 */

  /* 定义item之间的间距为20px */
  grid-gap: 20px;
}
.item{
  background: #ccc;
}

下面对代码中的 grid-template-rowsgrid-template-columnsgrid-gap 进行具体解释。

grid-template-rows & grid-template-columns

grid-template-rows 用来定义网格的行,每个值代表一行的高度,上面的 3 个 140px 就表示有三行,高度都是 140px。如下,定义四行,每行的高度分别为 140px、300px、200px、400px:

.grid {
  /* 定义四行 */
  grid-template-rows: 140px 300px 200px 400px;
}

同理,grid-template-columns 用来定义网格的列,每个值代表一列的宽度。

如遇到有规律的重复列宽或行高,还可以使用 grid 中新增的 repeat 函数来表示重复,如上面的 140px 的 3 * 6 网格:

.grid {
  grid-template-rows: repeat(3, 140px);
  grid-template-columns: repeat(6, 140px);
}

grid-gap

该属性是 grid-column-gap (列之间的间距)和 grid-row-gap (行之间的间距)的简写,其语法为:grid-gap: <grid-column-gap> <grid-row-gap>

.grid {
  grid-gap: 15px 30px;
}

/* 等同于 */
.grid {
  grid-column-gap: 15px;
  grid-row-gap: 30px;
}

如只有一个值,则行列间距相等,如 gird-gap: 20px;

第三步:items 定位

上面网格实现的效果图如下:

image.png

看起来,这跟我们要实现的效果相差甚远,但其实我们只差最后一步:将 items 定位到对应的网格中即可。

简单来说,grid 中的每个 items 都可以借助四条线来决定定位。这四条线分别是两条横线和两条竖线,其形成的交叉区域就是定位的位置。默认的话,一个 item 占据一个 cell,而如果一个 item 占据多个 cell,就需要进行额外的定位处理,如下图:

image.png

如上图中,里面使用了 grid-area 来实现定位,其四个值以右斜线分割,分别表示行开始线、列开始线、行结束线、列结束线。除了使用 grid-area 之外,还可以使用 grid-column 来指定两条列线,grid-row 来指定两条行线。

grid-column & grid-row

grid-column 属性是 grid-column-startgrid-column-end 的简写,表示列线的起始与结束,其语法为:grid-column: <start-line> / <end-line>;同样 grid-row 属性是 grid-row-startgrid-row-end 的简写,表示行线的起始与结束,其语法为:grid-row: <start-line> / <end-line>。它们都属于 grid items 的属性,用来定义如何合并 grid cell。如上面的 grid-area: 1 / 3 / 3 / 5 就可以写成:

.item2 {
  /* 行起始与结束 */
  grid-row-start: 1;
  grid-row-end: 3;

  /* 列起始与结束 */
  grid-column-start: 3;
  grid-column-end: 5;
}

合并 cell

现在我们已经知道如何合并 cell 了,那么先把需要合并的 items 罗列出来,分别是1、2、5、7、9。如下图:

image.png

第一个 item 元素单元格占了两列,第一列和第二列,那么其垂直列开始于第一条 line,结束于第三条 line,同样第五个 item 元素也是如此:

.item:nth-child(1),
.item:nth-child(5) {
  grid-column: 1 / 3; /* 起始于1,结束于3 */
}

而第二个 item 元素列和行都跨了两个,CSS 代码如下:

.item:nth-child(2) {
  grid-column: 3 / 5; /* column起始于3,结束于5 */
  grid-row: 1 / 3;  /* row起始于1,结束于3 */
}

同样第七个 item 元素行跨了两个,第九个 item 元素列跨了两个,CSS 代码如下:

.item:nth-child(7) {
  grid-column: 6;
  grid-row: 2 / 4; /* row起始于2,结束于4 */
}
.item:nth-child(9) {
  grid-column: 2 / 4; /* column起始于2,结束于4 */
}

最后效果图如下:

image.png

查看 demo

总结

一般来说,对于一个不怎么复杂的网格系统,无非是先设计出对应的网格系统,然后对 items 进行定位而已。涉及到的属性也无非是如何创建行和列,如何合并 cell,所以没那么复杂。当然这只是冰山一角而已,要玩转 grid,光这些还是远远不够的,下节将继续深入挖掘。

参考资料