网格布局 ——(语法篇)

1,369 阅读14分钟

这是HTML&CSS重点知识点合集第四篇,内容稍微有点多。其余文章见文末。

一、网格布局(Grid)

前面我们讲了 伸缩盒。我们讲到,伸缩盒是传统盒模型布局的一种代替,并且伸缩容器以及flex item的属性足以应付我们常用的各种布局模式,那为什么还会出现Grid网格布局呢?

可能你已经知道了,以往任何布局方式几乎都是一维布局,而Grid最大的特色就是采用了二维布局。当然,Grid的出现并不是用来替代flex-layout,而是对它的补充。前者擅长一维而后者擅长二维,它们需要各司其职。

二、浏览器兼容

我们来看看它的兼容性吧。从2017年3月份开始,Grid开始得到部分的浏览器的支持。

gridBrowserSupport

三、Grid和table

你可能会有疑问,Grid不就是设置行和列嘛?可是这些table不是都可以做嘛?那为啥还要有Grid?

从效果上来看,Grid布局和table一样都是把一部分区域通过行和列来划分成一小块一小块方格。但是实际上它们是有很大区别的:
1.首先,它们的本质区别在于Grid是用纯CSS实现的布局方式。而table布局依赖HTML标签,它有固定的结构。Grid可以直接使用 grid-template-columnsgrid-template-rows 属性来设置容器的行和列的长度。例如:

.container {
  display: grid;
  grid-template-columns: 50px 50px 50px;
  grid-template-rows: 50px 50px 50px;
}

当我们对一个容器设置上述样式,就会生成一个三行三列且每一小格长宽均为50px。点击这里 自己实践一下!

grid exp

2.其次,基于table固定标签的结构,它的布局就很不自由。但是Grid不同,使用它来对二维布局可以利用几行代码轻松实现,灵活性很高。

四、Grid布局语法

在具体讲Grid布局之前,我们先来认识几个术语。

第一、和伸缩盒一样,网格布局中也有容器这个概念。网格容器(Grid container),为其内容建立新的网格格式化上下文,容器外元素和容器内元素互不干扰。比如上图中盛装数字块的最外容器。

第二、网格线(Grid line),顾名思义,容器的水平垂直分割线。它构建出网格轨道、网格单元和网格区域。网格线是有数字索引的,可以自定义名称。

第三、网格轨道(Grid track),网格内容块之间水平或垂直的空间。

第四、网格单元格(Grid cell),网格内容的单元区块,是可以放置内容的最小区块。比如上图中的某一个数字块。

第五、网格区域(Grid area),以网格线为界划定区域。

(1). 网格容器

网格容器所有的属性:

  1. display
  2. grid-template-columns
  3. grid-template-rows
  4. grid-template-areas
  5. grid-template
  6. grid-column-gap
  7. grid-row-gap
  8. grid-gap
  9. justify-items
  10. align-items
  11. place-items
  12. justify-content
  13. align-content
  14. place-content
  15. grid-auto-columns
  16. grid-auto-rows
  17. grid-auto-flow
  18. grid

和伸缩盒不一样,在网格布局是一个二维布局,有着行和列的概念,所以我们接下来讲的容器属性是:如何定义网格容器以及如何定义网格的行数、列数以及网格的大小(长宽)。

1. display

.container {
  display: grid | inline-grid;
}

2. grid-template-columnsgrid-template-rows
如上面例子展示一样,它们用来定义网格的行数、列数、网格大小。比如我们需要定义一个四行四列的容器可以这样写:grid-template-columns: 50px 50px 50px 50px; grid-template-rows: 50px 50px 50px 50px;

当然,属性值除了可以使用 px 作为单位还可以使用 em% 来作为单位。在CSS3规范中,我们看到这样一段话:

A flexible length or <flex> is a dimension with the fr unit, which represents a fraction of the leftover space in the grid container. Tracks sized with fr units are called flexible tracks as they flex in response to leftover space similar to how flex items fill space in a flex container.

翻译过来就是说 fr 这个单位表示的是网格容器中剩余空间的一部分。大小为 fr 单位的‘轨道’称为弹性‘轨道’,它们会响应式的填充剩余的内容,和伸缩盒中的flex item会自动填充剩余空间这样的行为很相似。

假设设置一个三行四列的列表,希望最后一列自适应容器宽度就可以这样设置:

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

三行四列,最后一列自适

那如果希望第一列长度固定而后三列中第一列占剩余长度的四分之一,第二列占二分之一最后一列占四分之一应该该怎么写呢?

.container {
  display: grid;
  grid-template-columns: 50px 1fr 2fr 1fr;
  grid-template-rows: 50px 50px 50px;
}

fr用做分数

怎么样?神奇吧?

对于这两个属性,CSS3还提供了一个函数:repeat()。它的作用相信你一看就知道了:

.container {
  display: grid;
  grid-template-columns: 50px 1fr 2fr 1fr;
  grid-template-rows: repeat(3, 50px);
}

3. grid-template-areas

.container {
  grid-template-areas: "<grid-area-name> | . | none | ..." "...";
}

它用来定义网格区域,使用 grid-area 调用声明好的网格区域名称用来放置对应的网格项目。

举个栗子:

.item-a {
  grid-area: header;
}
.item-b {
  grid-area: main;
}
.item-c {
  grid-area: sidebar;
}
.item-d {
  grid-area: footer;
}

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

我们通过Grid item的属性 grid-area(后面会讲到)来为item设置相应的名称,然后再利用容器属性 grid-template-areas通过设置的名称对整个容器布局。下面就是布局的效果:

grid-area

4. grid-template
这是属性 grid-template-rowsgrid-template-columnsgrid-template-areas 三个属性的简写。

.container {
  grid-template: none | <grid-template-rows> / <grid-template-columns>;
}

当仅仅设置 grid-template-rowsgrid-template-columns 时,grid-template-areas 自动为 none。中间使用符号 / 隔开。比如前面一个例子就可以写成这样:

.container {
  display: grid;
  grid-template: repeat(3, 50px) / 50px 1fr 2fr 1fr;
}

grid-template 不会隐式的设置 grid-auto-columnsgrid-auto-rowsgrid-auto-flow,所以推荐使用 grid 属性来代替。

5. grid-row-gapgrid-column-gap
定义网格之间的间距(不包括grid item到容器边缘的间距)

6. justify-items

.container {
  justify-items: start | end | center | stretch; /* 默认值为stretch */
}

定义Grid item的内容在水平方向上的对齐方式(其值指的是在单元格内的表现)。比如:

.container {
  justify-items: start;
}

justify-items-start

.container {
  justify-items: center;
}

justify-items-center

和伸缩容器的 align-items 一样,它也能在子项目中使用属性 justify-self 进行覆盖。

7. align-items

.container {
  align-items: start | end | center | stretch; 
}

justify-items 差不多,只不过这个属性作用的是另一个轴方向。

.container {
  align-items: start;
}

align-items-start

同样的,这个属性值也可以利用子项目的属性 align-self 进行覆盖。

8. place-items
是属性 align-items 和属性 justify-items 的简写。中间使用 / 进行分隔。若只有一个值,那么将同时设置两个相同的值。

注:除了Edge,所有主流浏览器均支持该属性。

9. justify-content
有些时候,当我们对单元格的长设置了固定值,这时就有可能导致所有网格的长加起来不能撑满整个网格容器。比如下面这样:

justify-content-start

所以,justify-content 属性就运用而生。它主要就是设置容器中内容的排布方式的。

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

栗子如下:

.container {
  justify-content: space-around;
}

justify content space-around

10. align-content
和上一个属性相呼应,如果我们对每个单元格的高设置固定值,这就有可能导致所有单元格的高加起来没有填满整个容器。

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

align-content-start

11. place-content
前两个属性的简写形式,如果只设置一个值,则表示设置相同的值。

除Edge外,所有主流浏览器都支持这个属性。

12. grid-auto-columnsgrid-auto-rows
设置隐式网格轨道的大小。

.container {
  grid-auto-columns: <track-size> ...;
  grid-auto-rows: <track-size> ...;
  /* <track-size> 长度,可以是前面提到的任何表示形式 */
}

其实这个属性就是设置之后有可能动态添加进来的item的长度和高度。来看下面一个栗子: 首先,我们设置好高宽:

.container {
  grid-template-columns: 60px 60px;
  grid-template-rows: 90px 90px;
}

设置高宽高宽

现在我们来对Grid item设置一下它的位置(这两个属性接下来会讲到,用来设置item的位置,其中/两侧的值分别是网格轨道的起始位置和终点位置):

.item-a {
  grid-column: 1 / 2;
  grid-row: 2 / 3;
}
.item-b {
  grid-column: 5 / 6;
  grid-row: 2 / 3;
}

设置item位置

在这里我们设置了一个 item-b,并且容易看出,它在原先定义的网格中并不存在,于是就有了上图。看到,第三、四列的长度为0,且 item-b的这一列的长度也没设置(默认值为 auto,和前面已经定义的长度一样)。

如何解决第三、四列的长度问题呢?这里我们就可以利用属性 grid-auto-columns 来动态的设置后添加进来item的长度。

.container {
  grid-auto-columns: 60px;
}

自动设置长度

13. grid-auto-flow

.container {
  grid-auto-flow: row | column | row dense | column dense;
}

它定义了默认“流”方向,它的动作和flexbox里的 flex-direction 有些相似。

14. grid
它是属性 grid-template-rowsgrid-template-columnsgrid-template-areasgrid-auto-rowsgrid-auto-columns 以及grid-auto-flow 的缩写。

.container {
  grid: none | <grid-template> | <grid-template-rows> / [auto-flow && dense?] <grid-auto-columns>? | [auto-flow && dense?] <grid-auto-rows>? / <grid-template-columns>;
}

举个栗子就明白啦:

.container {
  grid: 100px 300px / 3fr 1fr;
}

/* 等同于下面: */
.container {
  grid-template-rows: 100px 300px;
  grid-template-columns: 3fr 1fr;
}

再比如:

.container {
  grid: auto-flow / 200px 1fr;
}

/* 等同于下面: */
.container {
  grid-auto-flow: row;
  grid-template-columns: 200px 1fr;
}

.container {
  grid: auto-flow dense 100px / 1fr 2fr;
}

/* 等同于 */
.container {
  grid-auto-flow: row dense;
  grid-auto-rows: 100px;
  grid-template-columns: 1fr 2fr;
}

(2). 网格项目

所有Grid item属性:

  1. grid-column-start
  2. grid-column-end
  3. grid-row-start
  4. grid-row-end
  5. grid-column
  6. grid-row
  7. grid-area
  8. justify-self
  9. align-self
  10. place-self

1. grid-column-startgrid-column-endgrid-row-startgrid-row-end
这四个属性是用来定义item的四周位置的。

.item {
  grid-column-start: <line> | <number> | <name> | span <number> | span <name> | auto;
  grid-column-end: <line> | <number> | <name> | span <number> | span <name> | auto;
  grid-row-start: <line> | <number> | <name> | span <number> | span <name> | auto;
  grid-row-end: <line> | <number> | <name> | span <number> | span <name> | auto;
}

其中 <line> 的值可以是一个数字,表示第几根网格线,也可以是已经定义过的网格线名称。

举个栗子:

.item-a {
  grid-column-start: 2;
  grid-column-end: five;
  grid-row-start: row1-start;
  grid-row-end: 3;
}

grid-columns-start | grid-row-start1

再比如:

.item-b {
  grid-column-start: 1;
  grid-column-end: span col4-start;
  grid-row-start: 2;
  grid-row-end: span 2;
}

grid-columns-start | grid-row-start2

grid-column-end 或者 grid-row-end 没有被设置,则默认的跨越步长为1。Grid item有时候可能会相互重叠,这时候可以使用 z-index 控制其层叠顺序。

2. grid-columngrid-row
前者是属性 grid-column-startgrid-column-end 的简写,后者是属性 grid-row-startgrid-row-end 的简写。

.item {
  grid-column: <start-line> / <end-line> | <start-line> / span <value>;
  grid-row: <start-line> / <end-line> | <start-line> / span <value>;
}

例如:

.item-c {
  grid-column: 3 / span 2;
  grid-row: third-line / 4;
}

grid-column grid-row

3. grid-area
这个属性我们前面提到过,它可以用来定义Grid item的名称,用来给属性 grid-template-areas 设置布局。当然,它也可以当作属性 grid-row-startgrid-column-startgrid-row-endgrid-column-end 的简写。

.item {
  grid-area: <name> | <row-start> / <column-start> / <row-end> / <column-end>;
}

关于 <name> 的栗子上面已经有了,这里不再赘述。下面举第二个值的栗子:

.item-d {
  grid-area: 1 / col4-start / last-line / 6;
}

grid-area

4. justify-selfalign-self
前面也提到啦,这两个属性都是子元素对父元素设置的 justify-itemsalign-items 进行覆盖。这里就不再举栗子惹~

5. place-self
这个属性还是比较厉(sao)害(qi)的~ 看用法:

.item-a {
  place-self: auto | <align-self> / <justify-self>;
}

栗子:

.item-a {
  place-self: center;
}

place-self center

.item-a {
  place-self: center stretch;
}

place-self center stretch

最后,Grid item对属性 floatdisplay: inline-blockdisplay: table-cellvertical-aligncolumn-* 失效。

(3). 网格布局带来的函数和关键字

  • 当我们设置长度时,我们可以使用像 px、res 、%,这些我们以往使用的单位。我们还可以使用关键字:min-contentmax-contentauto,当然,可能最有用的还是 fr
  • 我们也可以通过函数 minmax() 来设置一个大小的区间段。
  • repeat(),前面已经有讲过,这里不再赘述。

五、网格布局中的动画

根据CSS网格布局模型Level 1,有5个网格动画属性,分别是: grid-gapgrid-row-gapgrid-column-gapgrid-template-columnsgrid-template-rows

我看可以利用 @keyframes 规则,通过改变这五个属性大小达到想要的动画效果。

这里有个使用属性 transition 来实现动画的栗子:动画demo

六、伸缩盒布局和网格布局带来的思考

(1). 为什么网格布局容器中有属性 justify-items 而伸缩容器没有属性 justify-items

首先,我们来对比一下伸缩容器的 justify-content 和网格容器的 justify-content:

flexbox justify-content space-around

Grid justify-content space-around

容易看出,在伸缩容器中,每一个item是一个排列单元,而在网格容器中,每一列是一个排列单元。

现在我们再来看看网格容器的属性:justify-items

Grid justify-items start

它的作用是在水平方向设置item内容的对齐方式。如果设置为 start,则表示所有item的内容靠近左网格线。如果设置为 center,则表示将内容在水平方向居中显示。在网格这个二维空间中,显然它是非常有用的属性。而如果我们也为伸缩布局设置这个属性,很显然和它的属性 justify-content 的作用发生的重叠,且效果没有前者好。因此,在伸缩容器中定义 justify-items 是没有必要的。

(2). 为什么最好不要使用flex布局作为整个页面的布局?

当我们有大量的内容绘制到页面上、或者内容更改的,在2g或者网络加载不稳定的时候,页面是不稳定的。网格布局会以流的形式被获取到,且能使我们在页面全部加载出来之前就看到内容,而flex布局由于其相对布局的特征会导致布局的重排。

当然并不是说网格布局就一定不会引发重排,它是有前提的:网格的划分是预先确定的,比如根据视窗高度来确定。

假如内容是根据内容弹性改变的,那么也无法避免发生重排。

ps: 最后的最后,终于整理完啦,满满的收获呀。当然啦,希望你也能有所收获~
这里给大家推荐一个有趣的Grid游戏吧:grid garden,还蛮有意思的,用来入门简直不要太好~


这是HTML&CSS重点知识点合集的其中一篇,合集其它文章:
1.语义化HTML的重要性
2.传说中的BFC
3.CSS布局神器——伸缩盒(语法篇)


参考文章:
1.未来布局之星Grid-2017-09-24 by 考拉海购前端团队
2.CSS Grid 网格布局教程-2019-03-25 by 阮一峰

推荐阅读:
1.A Complete Gide to Grid-2019-9-13 by Chris