CSS Grid,是CSS中最强大的二维布局系统,可以同时处理行和列,让复杂的网页布局变得简单优雅。

在CSS Grid出现之前,我们做网页布局,用的是table、float、position、inline-block这些方式。这些方式,要么语义不好,要么需要很多hack,要么实现复杂布局非常麻烦。后来有了Flexbox,一维布局变得简单了,但是二维布局(同时控制行和列)还是不够方便。

CSS Grid的出现,彻底改变了这一切。它是专门为二维布局设计的,可以轻松实现各种复杂的网页布局,而且代码简洁,语义清晰。

2017年,Chrome、Firefox、Safari、Edge等主流浏览器都开始支持CSS Grid。到了2018年,CSS Grid已经可以在生产环境中放心使用了。今天,我想从基础到高级,详细讲解一下CSS Grid的配置和使用。

一、CSS Grid的基本概念

在学习CSS Grid之前,先搞清楚几个基本概念:

1. 网格容器(Grid Container)

网格容器,就是应用了display: grid或者display: inline-grid的元素。它是所有网格项目的父元素。

.container {
  display: grid;
}

2. 网格项目(Grid Item)

网格项目,就是网格容器的直接子元素。注意,是直接子元素,孙子元素不会成为网格项目。

<div class="container">
  <div class="item">1</div>  <!-- 网格项目 -->
  <div class="item">2</div>  <!-- 网格项目 -->
  <div class="item">3</div>  <!-- 网格项目 -->
</div>

3. 网格线(Grid Line)

网格线,是划分网格的线。水平的网格线划分行,垂直的网格线划分列。网格线是从1开始编号的,也可以用负数从后往前编号。

比如,一个3列2行的网格,就有4条垂直网格线(列线)和3条水平网格线(行线)。

4. 网格轨道(Grid Track)

网格轨道,是两条相邻网格线之间的空间。简单说,就是行和列。水平的轨道是行,垂直的轨道是列。

5. 网格单元格(Grid Cell)

网格单元格,是两条相邻行线和两条相邻列线交叉形成的空间,是网格中最小的单位。

6. 网格区域(Grid Area)

网格区域,是由任意多条网格线围成的矩形区域,可以包含一个或多个网格单元格。

二、网格容器的属性

网格容器的属性,是用来定义网格的整体结构的。下面逐个讲解:

1. display

定义一个网格容器。

.container {
  display: grid;  /* 块级网格容器 */
  /* 或者 */
  display: inline-grid;  /* 行内网格容器 */
}

2. grid-template-columns 和 grid-template-rows

这两个属性,用来定义网格的列和行的大小。

.container {
  display: grid;
  /* 定义3列,每列100px */
  grid-template-columns: 100px 100px 100px;
  /* 定义2行,第一行50px,第二行100px */
  grid-template-rows: 50px 100px;
}

除了固定的像素值,还可以用百分比、auto、fr等单位:

  • 百分比:相对于网格容器的宽度/高度。
  • auto:由内容自动决定大小。
  • fr:fraction的缩写,表示剩余空间的比例。这是Grid中非常好用的一个单位。
.container {
  display: grid;
  /* 3列,第一列固定200px,剩余空间第二列占1份,第三列占2份 */
  grid-template-columns: 200px 1fr 2fr;
  /* 3行,高度自动 */
  grid-template-rows: auto auto auto;
}

还可以用repeat()函数简化重复的定义:

.container {
  display: grid;
  /* 重复3次,每次1fr,相当于 1fr 1fr 1fr */
  grid-template-columns: repeat(3, 1fr);
  /* 重复2次,每次100px 50px,相当于 100px 50px 100px 50px */
  grid-template-columns: repeat(2, 100px 50px);
}

3. grid-template-areas

这个属性,用来给网格区域命名,然后让网格项目引用这些区域名称来定位。这是CSS Grid中非常强大、非常直观的一个功能。

.container {
  display: grid;
  grid-template-columns: 200px 1fr 200px;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "header header header"
    "sidebar main aside"
    "footer footer footer";
}

.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.aside { grid-area: aside; }
.footer { grid-area: footer; }

上面的代码,就定义了一个经典的三列布局:头部占满一行,中间是侧边栏、主内容、右侧栏,底部占满一行。用grid-template-areas,布局结构一目了然,非常直观。

注意,用.可以表示一个空的单元格:

grid-template-areas:
  "header header header"
  "sidebar . aside"
  "footer footer footer";

中间的主内容区域是空的。

4. grid-column-gap / grid-row-gap / grid-gap

这三个属性,用来定义网格列之间和行之间的间距( gutter )。

.container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-column-gap: 20px;  /* 列间距20px */
  grid-row-gap: 10px;     /* 行间距10px */
  /* 或者用grid-gap简写,第一个是行间距,第二个是列间距 */
  grid-gap: 10px 20px;
  /* 如果只有一个值,行间距和列间距都是这个值 */
  grid-gap: 15px;
}

注意,这个间距只在网格项目之间,不会在容器边缘。

5. justify-items

定义网格项目在单元格内的水平对齐方式。

.container {
  justify-items: start;   /* 左对齐 */
  justify-items: end;     /* 右对齐 */
  justify-items: center;  /* 居中 */
  justify-items: stretch; /* 拉伸填满(默认) */
}

6. align-items

定义网格项目在单元格内的垂直对齐方式。

.container {
  align-items: start;   /* 顶部对齐 */
  align-items: end;     /* 底部对齐 */
  align-items: center;  /* 居中 */
  align-items: stretch; /* 拉伸填满(默认) */
}

7. place-items

justify-itemsalign-items的简写。

.container {
  place-items: center center;  /* 垂直居中,水平居中 */
  place-items: center;         /* 两个都是center */
}

8. justify-content

当网格的总大小小于容器大小的时候,定义整个网格在容器内的水平对齐方式。

.container {
  justify-content: start;        /* 左对齐 */
  justify-content: end;          /* 右对齐 */
  justify-content: center;       /* 居中 */
  justify-content: stretch;      /* 拉伸 */
  justify-content: space-around; /* 每个项目两侧间距相等 */
  justify-content: space-between;/* 两端对齐,项目之间间距相等 */
  justify-content: space-evenly; /* 所有间距相等 */
}

9. align-content

类似justify-content,但是是垂直方向。

.container {
  align-content: start;
  align-content: end;
  align-content: center;
  align-content: stretch;
  align-content: space-around;
  align-content: space-between;
  align-content: space-evenly;
}

10. place-content

justify-contentalign-content的简写。

11. grid-auto-columns / grid-auto-rows

当网格项目超出了显式定义的网格(也就是grid-template-columns/rows定义的范围),会自动创建隐式网格。这两个属性,用来定义隐式网格的列和行的大小。

.container {
  display: grid;
  grid-template-columns: 100px 100px;  /* 显式定义2列 */
  grid-auto-rows: 50px;  /* 隐式行的高度都是50px */
}

如果有3个项目,前两个在第一行,第三个会自动到第二行,第二行的高度就是50px。

12. grid-auto-flow

定义网格项目的自动排列方向。

.container {
  grid-auto-flow: row;     /* 按行排列(默认),排满一行换下一行 */
  grid-auto-flow: column;  /* 按列排列,排满一列换下一列 */
  grid-auto-flow: row dense;  /* 按行排列,并且尽量填满空隙 */
}

dense关键字,可以让后面的小项目往前填充前面留下的空隙,让网格更紧凑。

三、网格项目的属性

网格项目的属性,是用来控制单个项目在网格中的位置和大小的。

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

这四个属性,用来定义网格项目占据哪些网格线。

.item {
  grid-column-start: 1;  /* 从第1条列线开始 */
  grid-column-end: 3;    /* 到第3条列线结束(占2列) */
  grid-row-start: 1;     /* 从第1条行线开始 */
  grid-row-end: 2;       /* 到第2条行线结束(占1行) */
}

还可以用span关键字,表示跨越多少个轨道:

.item {
  grid-column-start: 1;
  grid-column-end: span 2;  /* 跨越2列 */
  /* 或者 */
  grid-column: 1 / span 2;  /* 简写 */
}

也可以用网格区域的名称:

.item {
  grid-column-start: header-start;
  grid-column-end: header-end;
}

2. grid-column / grid-row

上面四个属性的简写。

.item {
  grid-column: 1 / 3;       /* 从第1条列线到第3条列线 */
  grid-row: 1 / 2;          /* 从第1条行线到第2条行线 */
  grid-column: 1 / span 2;  /* 从第1条列线开始,跨越2列 */
}

3. grid-area

给网格项目命名,或者引用grid-template-areas中定义的区域名称。

/* 引用区域名称 */
.item {
  grid-area: header;
}

/* 也可以用四个值,分别是 row-start / column-start / row-end / column-end */
.item {
  grid-area: 1 / 1 / 2 / 3;
}

4. justify-self

定义单个网格项目在单元格内的水平对齐方式,会覆盖容器的justify-items

.item {
  justify-self: start;
  justify-self: end;
  justify-self: center;
  justify-self: stretch;
}

5. align-self

定义单个网格项目在单元格内的垂直对齐方式,会覆盖容器的align-items

.item {
  align-self: start;
  align-self: end;
  align-self: center;
  align-self: stretch;
}

6. place-self

justify-selfalign-self的简写。

7. z-index

网格项目可以重叠,用z-index控制重叠时的层级。

.item-a {
  grid-column: 1 / 3;
  grid-row: 1 / 2;
  z-index: 1;
}
.item-b {
  grid-column: 2 / 4;
  grid-row: 1 / 2;
  z-index: 2;  /* b在a上面 */
}

四、常见布局实现

了解了基本属性之后,我们来看看用CSS Grid怎么实现一些常见的布局。

1. 三列布局(侧边栏+主内容+右侧栏)

.container {
  display: grid;
  grid-template-columns: 200px 1fr 200px;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "header header header"
    "sidebar main aside"
    "footer footer footer";
  min-height: 100vh;
}

2. 响应式卡片布局

auto-fillminmax(),可以实现自动适应容器宽度的卡片布局,不需要媒体查询。

.container {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  grid-gap: 20px;
}

这个布局的意思是:每列最小250px,最大1fr(平分剩余空间),自动填充尽可能多的列。容器宽的时候列多,容器窄的时候列少,自动响应式。

auto-fillauto-fit的区别:auto-fill会保留空的列轨道,auto-fit会把空的列轨道折叠,让现有项目拉伸填满。大部分时候用auto-fit效果更好。

3. 圣杯布局

.container {
  display: grid;
  grid-template: auto 1fr auto / 200px 1fr 200px;
  grid-template-areas:
    "header header header"
    "nav main aside"
    "footer footer footer";
  min-height: 100vh;
}

4. 瀑布流布局(近似)

CSS Grid可以做近似的瀑布流布局(严格的瀑布流需要CSS Masonry,目前还在实验阶段):

.container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: 100px;
  grid-gap: 10px;
}
.item:nth-child(3n+1) { grid-row: span 2; }
.item:nth-child(3n+2) { grid-row: span 3; }

五、高级技巧和最佳实践

1. 用fr单位做灵活布局

fr单位是CSS Grid的一大亮点,可以轻松实现各种灵活的比例布局。比如,一个经典的布局:侧边栏固定宽度,主内容自适应。

.container {
  display: grid;
  grid-template-columns: 250px 1fr;
}

再比如,三列等宽:

grid-template-columns: repeat(3, 1fr);

2. 用minmax()做最小最大限制

minmax(min, max)函数,可以定义轨道的最小和最大尺寸。

grid-template-columns: minmax(200px, 1fr) 2fr;

第一列最小200px,最大1fr。这样,容器很窄的时候,第一列也不会小于200px。

3. 用auto-fit + minmax做无媒体查询的响应式

前面提到过,这是CSS Grid中最实用的技巧之一:

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 20px;
}

不需要任何媒体查询,就能实现从1列到多列的自动响应式布局。

4. 用grid-template-areas做语义化布局

grid-template-areas让布局结构在CSS中一目了然,非常直观,也方便维护和修改。推荐在做整体页面布局的时候使用。

5. Grid和Flex配合使用

Grid和Flex不是互斥的,而是互补的。Grid适合做整体的二维页面布局,Flex适合做一维的组件内部布局。最佳实践是:外层用Grid做页面整体布局,内层组件用Flex做内部排列。

6. 注意浏览器兼容性

到2018年,主流浏览器的最新版本都已经支持CSS Grid了。但是,如果需要兼容旧版本浏览器(比如IE11),需要注意:

  • IE11支持的是旧版本的Grid语法,和新标准有差异。
  • 可以用Autoprefixer自动处理IE11的前缀和语法转换。
  • 如果不需要兼容IE11,可以放心使用标准的Grid语法。

7. 用命名网格线提高可维护性

可以给网格线命名,然后用名称来定位项目,比用数字更语义化:

.container {
  grid-template-columns: [sidebar-start] 200px [sidebar-end main-start] 1fr [main-end];
}
.sidebar {
  grid-column: sidebar-start / sidebar-end;
}

六、写在最后

CSS Grid,是CSS布局的一次革命。它让复杂的二维网页布局变得简单、优雅、可维护。

在CSS Grid之前,我们做布局,需要用各种hack,需要写很多冗余的代码,需要处理各种浏览器兼容性问题。有了CSS Grid,很多以前需要花半天才能实现的布局,现在几行代码就能搞定。

当然,CSS Grid的属性比较多,刚开始学的时候可能会觉得有点复杂。但是,只要掌握了核心的几个属性(grid-template-columns/rowsgrid-template-areasgrid-gapgrid-column/row),就能应对大部分布局场景了。其他的属性,可以在需要的时候再查文档学习。

2018年,CSS Grid已经足够成熟,可以在生产环境中放心使用了。如果你还没有用过CSS Grid,强烈建议你尝试一下。用过之后,你可能就再也回不到float布局的时代了。

最后,用一句话来总结这篇文章:"CSS Grid是目前最强大的CSS二维布局系统,掌握了它,网页布局就不再是难题。从基础的容器和项目属性,到高级的响应式技巧,循序渐进地学习和实践,你就能熟练运用CSS Grid,构建出优雅、灵活、可维护的网页布局。"

希望这篇文章,能帮助你入门和掌握CSS Grid。