css grid 网格布局完整介绍

英文原文:A Complete Guide to Grid,感谢zhaozhiming的翻译。

介绍

CSS Grid 布局(又叫“Grid”),是一个基于网格的二维布局系统,目的是为了完全改变我们基于网格设计用户界面的方式。CSS 可以用来做我们的网页布局,但它在这一方面做的不是很好。开始的时候我们使用tables, 然后使用floatspositioninginline-block,但这些方法本质上都是 hack 的方法并缺少一些重要功能(比如垂直居中)。Flexbox帮助我们解决了问题,但它是简单的一维布局,而不是复杂的二维布局(实际上 Flexbox 和 Grid 可以很好地组合起来使用)。Grid 是第一个专门为了解决那些我们一直使用 hack 手段而导致的页面布局问题而创建的 CSS 模块。

我写这篇文章主要收到两个事情启发,第一个是Rachel Andrew写的一本好书——《Get Ready for CSS Grid Layout》,这本书把对 Grid 全面而清晰的介绍作为全书的基调,我高度推荐大家去买这本书来读一下。我另外一件受启发的事情是Chris CoyierFlexbox 的完整介绍,这是我推荐学习 flexbox 的首选资源,它帮助了很多人,当你用 Google 搜索 flexbox 时可以从它的搜索结果看出其影响范围。你可以看到那篇文章跟我的文章有很多相似的地方,因为我这篇文章就是通过模仿那篇最好的文章来写的。(译者注:可以看到这两篇文章都是按照两列分布的方式来介绍 flexbox 和 Grid。)

我这篇文章的目的是为了介绍 Grid 在最新规范中的概念,所以我不会涵盖过时的 IE 语法,并且当规范更新时我将尽力更新这篇文章。

基础和浏览器支持

开始使用 Grid 非常简单,你只需要通过display: grid来定义一个容器元素作为网格,再通过grid-template-columnsgrid-templaet-rows设置列和行的大小,然后通过grid-columngrid-row来设置网格的子元素,grid 元素的顺序对其实现的效果没有任何影响。你的 CSS 可以任意调节它们的顺序,这可以让你很方便地在媒体查询中重新编排你的网格。想象一下在你的整个页面中定义了一个布局,然后通过几行 CSS 代码就可以重新编排出另外一个布局来适应另外一个屏幕,所以说 Grid 是有史以来最强大一个的 CSS 模块。

理解 Grid 最重要的一件事情是现在还不能把它用在生产环境。它现在还只是一个 W3C 的在制品草稿,还没有任何浏览器默认是支持它的。IE10 和 11 可以支持它,但它们是用过时的语法做的一个老旧的实现。最好地使用 Grid 的方式是设置 Chrome,Opera 或者 Firefox 的特殊标志来启用它。在 Chrome 中,在地址栏输入chrome://flags然后将experimental web platform features选项设置为enable,这个方法同样适用于 Opera(opera://flags),在 Firefox 中,将layout.css.grid选项设置为可用。

截至2017年3月,许多浏览器都提供了原生的、无需前缀的CSS Grid支持:Chrome(包括Android),Firefox,Safari(包括iOS)、Opera 和 Edge。另一方面,Internet Explorer 10和11支持它,但是它用的是一个过时的语法实现的。

这是一个支持的浏览器表格,详情见caniuse.com

Chrome Safari Firefox Opera IE Android iOS
29+ (Behind flag) 10.1+ 40+ (Behind flag) 28+ (Behind flag) 10+ (Old syntax) 62+ 10.3+

除了微软,其他浏览器好像不想太早实现 Grid 直到规范完全成熟为止,这是一件好事,这意味着我们不用担心以后使用 Grid 要使用多种语法。
在生产环境使用 Grid 只是时间上的问题,但现在是时候可以学习它了。

重要的术语

在开始了解 Grid 的概念之前先理解其相关的术语是很重要的,因为这里涉及的概念都有点相似,所以如果你不记住它们在规范中的定义的话会很容易被搞混,但请不用担心,这里的术语并不多。

网格容器(grid container)

网格容器是指这个元素使用了display: grid,它是所有网格元素的直接父级,在这个例子container的元素就是网格容器。

1
2
3
4
5
<div class="container">
<div class="item item-1"></div>
<div class="item item-2"></div>
<div class="item item-3"></div>
</div>

网格项(grid item)

网格项是指网格容器的子元素(比如其直接后代),在下面的例子中item的元素是网格项,但sub-item的元素不是。

1
2
3
4
5
6
7
<div class="container">
<div class="item"></div>
<div class="item">
<p class="sub-item"></p>
</div>
<div class="item"></div>
</div>

网格线(grid line)

分隔的线组成了网格的结构。它们可以是垂直的(“列网格线”)或者水平的(“行网格线”),也可以在行或列的任一边。下面的例子中黄色的线是一个列网格线的例子。

网格轨道(grid track)

网格轨道是指两根毗邻网格线中间的位置,你可以认为是网格的行或者列。下面例子的中网格轨道是第二和第三行网格线中间的位置。

网格单元(grid cell)

网格单元是指两根毗邻的行网格线和列网格线中间的位置,它是一个单独的网格“单元”,下面的例子中网格单元是指第 1 和 2 根行网格线和第 2 和 3 根列网格线中间的位置。

网格区域(grid area)

网格区域是指 4 根网格线包围的空间,一个网格空间可能由任意数量的网格单元构成。下面的例子中网格区域是指在第 1 和 3 的行网格线和第 1 和 3 列网格线中间的位置。

网格容器的属性

display

定义一个元素为网格容器并为其内容创建一个新的网格格式环境。

值有:

  • grid - 生成一个块级别的网格
  • inline-grid - 生成一个内联级别的网格
  • subgrid - 如果你的网格容器是它自己的一个网格子项(比如内嵌的网格),你可以使用这个属性来表示你想要从其父级来获取行和列的大小而不是自己来指定它们。
1
2
3
.container{
display: grid | inline-grid | subgrid;
}

注意:column, float, clearvertical-align对网格容器没有效果。

grid-template-columns与grid-template-rows

通过空格分隔的一系列值来定义网格的行和列,这些值相当于轨迹大小,它们之间的距离相当于网格线。

值有:

  • <track-size> - 可以是一个长度,百分比或者是网格中自由空间的份数(使用fr这个单位)
  • <line-name> - 一个你选择的任意名字
1
2
3
4
.container{
grid-template-columns: <track-size> ... | <line-name> <track-size> ...;
grid-template-rows: <track-size> ... | <line-name> <track-size> ...;
}

举例:

当你在轨迹值中间留空格,网格线将被自动以数字命名:

1
2
3
4
.container{
grid-template-columns: 40px 50px auto 50px 40px;
grid-template-rows: 25% 100px auto;
}

但你可以给网格线指定一个名字,注意网格线命名时的中括号语法:

1
2
3
4
.container{
grid-template-columns: [first] 40px [line2] 50px [line3] auto [col4-start] 50px [five] 40px [end];
grid-template-rows: [row1-start] 25% [row1-end] 100px [third-line] auto [last-line];
}

注意一根网格线可以有多个名字,例如在下面的例子中第二根线有两个名字:row1-endrow2-start

1
2
3
.container{
grid-template-rows: [row1-start] 25% [row1-end row2-start] 25% [row2-end];
}

如果你定义了容器的重复部分,你可以使用repeat()方法来生成多个相同值:

1
2
3
.container{
grid-template-columns: repeat(3, 20px [col-start]) 5%;
}

它等价于:

1
2
3
.container{
grid-template-columns: 20px [col-start] 20px [col-start] 20px [col-start] 5%;
}

fr单位允许你将网格容器中的自由空间设置为一个份数,举个例子,下面的例子将把网格容器的每个子项设置为三分之一。

1
2
3
.container{
grid-template-columns: 1fr 1fr 1fr;
}

自由空间是在所有固定项确定后开始计算的,在下面的例子中自由空间是fr单位的总和但不包括50px

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

grid-template-areas

通过引用在grid-area属性中指定的网格区域名字来定义网格模板。重复网格区域的名字将让内容跨越那些单元。一个句点表示一个空单元,语法本身提供了一个可视化的结构网格。

值有:

  • <grid-area-name> - 在grid-area中指定的网格区域名字
  • . - 一个句点表示一个空的网格单元
  • none - 没有网格区域被定义
1
2
3
4
.container{
grid-template-areas: "<grid-area-name> | . | none | ..."
"..."
}

举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.item-a{
grid-area: header;
}
.item-b{
grid-area: main;
}
.item-c{
grid-area: sidebar;
}
.item-d{
grid-area: footer;
}

.container{
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"
}

这将创建一个 4 乘以 3 的网格,第一行由header区域组成,中间一行由 2 个main区域和一个空单元和一个sidebar区域组成,最后一行由footer区域组成。

在你定义的每一行都需要拥有相同的单元格。

你可以使用任意毗邻的阶段来声明一个单独的空单元,只要这些阶段中间没有空间都可以认为是一个单独的单元。

注意,在这里你的语法只是命名了区域但没有对网格线进行命名,当你使这种语法时,区域任意一边的线会被自动命名。如果你的网格区域的名字是foo,然么网格的开始行和开始列网格线的名字将会是foo-start,并且它的最后一行和最后一列的网格线名字是foo-end。这意味着一些网格线可能有多个名字,比如上面那个例子中最左边的线,它会有三个名字分别是:header-startmain-startfooter-start

grid-column-gap

grid-row-gap

指定网格线的大小,你可以认为它就是设置行和列中间沟槽的宽度。

值有:

  • <line-size> - 一个长度值
1
2
3
4
.container{
grid-column-gap: <line-size>;
grid-row-gap: <line-size>;
}

举例:

1
2
3
4
5
6
.container{
grid-template-columns: 100px 50px 100px;
grid-template-rows: 80px auto 80px;
grid-column-gap: 10px;
grid-row-gap: 15px;
}

只会创建行和列的沟槽,不包括边缘。

grid-gap

一个grid-column-gap + grid-row-gap的简称。

值有:

  • <grid-column-gap><grid-row-gap> - 长度值
1
2
3
.container{
grid-gap: <grid-column-gap> <grid-row-gap>;
}

举例:

1
2
3
4
5
.container{
grid-template-columns: 100px 50px 100px;
grid-template-rows: 80px auto 80px;
grid-gap: 10px 15px;
}

如果没有写grid-row-gap,那么它的值将和grid-column-gap的一样。

justify-items

让网格子项的内容和列轴对齐(align-items则相反,是和行轴对齐),这个值对容器里面的所有网格子项都有用。

值有:

  • start - 内容和网格区域的左边对齐
  • end - 内容和网格区域的右边对齐
  • center - 内容和网格区域的中间对齐
  • stretch - 填充整个网格区域的宽度(默认值)
1
2
3
.container{
justify-items: start | end | center | stretch;
}

举例:

1
2
3
.container{
justify-items: start;
}
1
2
3
.container{
justify-items: end;
}
1
2
3
.container{
justify-items: center;
}
1
2
3
.container{
justify-items: stretch;
}

可以通过justify-self属性把这个行为设置到单独的网格子项。

align-items

让网格子项的内容和行轴对齐(justify-items则相反,是和列轴对齐),这个值对容器里面的所有网格子项都有用。

值有:

  • start - 内容和网格区域的上边对齐
  • end - 内容和网格区域的下边对齐
  • center - 内容和网格区域的中间对齐
  • stretch - 填充整个网格区域的高度(默认值)
1
2
3
.container{
align-items: start | end | center | stretch;
}

举例:

1
2
3
.container{
align-items: start;
}
1
2
3
.container{
align-items: end;
}
1
2
3
.container{
align-items: center;
}
1
2
3
.container{
align-items: stretch;
}

可以通过align-self属性把这个行为设置到单独的网格子项。

justify-content

有时候你的网格总大小可能会比它的网格容器的容量小,这可能是你的所有网格子项都使用了固定值比如px来确定大小,在这个情况下你可以在网格容器中设置网格的对齐方式。这个属性将网格和列轴对齐(和align-content相反,它是和行轴对齐)。

值有:

  • start - 网格在网格容器左边对齐
  • end - 网格在网格容器右边对齐
  • center - 网格在网格容器中间对齐
  • stretch - 改变网格子项的容量让其填充整个网格容器宽度
  • space-around - 在每个网格子项中间放置均等的空间,在始末两端只有一半大小
  • space-between - 在每个网格子项中间放置均等的空间,在始末两端没有空间
  • space-evenly - 在每个网格子项中间放置均等的空间,包括始末两端
1
2
3
.container{
justify-content: start | end | center | stretch | space-around | space-between | space-evenly;
}

举例:

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

align-content

有时候你的网格总大小可能会比它的网格容器的容量小,这可能是你的所有网格子项都使用了固定值比如px来确定大小,在这个情况下你可以在网格容器中设置网格的对齐方式。这个属性将网格和行轴对齐(和justify-content相反,它是和列轴对齐)。

值有:

  • start - 网格在网格容器上边对齐
  • end - 网格在网格容器下边对齐
  • center - 网格在网格容器中间对齐
  • stretch - 改变网格子项的容量让其填充整个网格容器高度
  • space-around - 在每个网格子项中间放置均等的空间,在始末两端只有一半大小
  • space-between - 在每个网格子项中间放置均等的空间,在始末两端没有空间
  • space-evenly - 在每个网格子项中间放置均等的空间,包括始末两端
1
2
3
.container{
align-content: start | end | center | stretch | space-around | space-between | space-evenly;
}
1
2
3
.container{
align-content: start;
}
1
2
3
.container{
align-content: end;
}
1
2
3
.container{
align-content: center;
}
1
2
3
.container{
align-content: stretch;
}
1
2
3
.container{
align-content: space-around;
}
1
2
3
.container{
align-content: space-between;
}
1
2
3
.container{
align-content: space-evenly;
}

grid-auto-columns

grid-auto-rows

指定自动生成的网格轨道的大小(又叫隐式网格轨道),当你精确指定行和列的位置大于定义的网格(通过 grid-template-rows/grid-template-columns)时隐式网格轨道会被创建。

值有:

  • <track-size> - 可以是一个长度,百分比或者是一个网格中自由空间的份数(通过使用fr单位)。
1
2
3
4
.container{
grid-auto-columns: <track-size> ...;
grid-auto-rows: <track-size> ...;
}

为了说明隐式网格轨道如何被创建,思考一下这个:

1
2
3
4
.container{
grid-template-columns: 60px 60px;
grid-template-rows: 90px 90px
}

这里创建了 2 x 2 的网格。

但现在想象你使用grid-columngrid-row来定位你的网格项,就像这样:

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

我们告诉.item-b在第 5 列网格线开始第 6 列网格线结束,但我们还没有定义第 5 或者第 6 列。因为我们引用的线不存在,0 宽度的隐式网格轨道将被创建来填充这些空缺。我们可以使用grid-auto-columnsgrid-auto-rows来指定这些隐式网格轨道的宽度:

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

grid-auto-flow

如果你有网格项没有明确地放置在网格中,自动布局算法会将网格项自动放置起来,这个属性控制自动布局算法如何工作。

值有:

  • row - 告诉自动布局算法在每一行中依次填充,必要时添加新行
  • column - 告诉自动布局算法在每一列中依次填充,必要时添加新列
  • dense - 告诉自动布局算法如果更小的子项出现时尝试在网格中填补漏洞
1
2
3
.container{
grid-auto-flow: row | column | row dense | column dense
}

注意dense可能让你的网格子项出现错乱。

举个例子:

考虑一下这个 HTML:

1
2
3
4
5
6
7
<section class="container">
<div class="item-a">item-a</div>
<div class="item-b">item-b</div>
<div class="item-c">item-c</div>
<div class="item-d">item-d</div>
<div class="item-e">item-e</div>
</section>

你定义一个 5 列 2 行的网格,并设置grid-auto-flowrow(这也是默认值):

1
2
3
4
5
6
.container{
display: grid;
grid-template-columns: 60px 60px 60px 60px 60px;
grid-template-rows: 30px 30px;
grid-auto-flow: row;
}

当在网格中放置子项时,你只能为其中 2 个指定斑点:

1
2
3
4
5
6
7
8
.item-a{
grid-column: 1;
grid-row: 1 / 3;
}
.item-e{
grid-column: 5;
grid-row: 1 / 3;
}

因为我们设置grid-auto-flowrow,我们的网格看起来就像这样,注意这三个我们没有放置的子项(item-bitem-citem-d)将如何以行的方式流动的:

如果我们将grid-auto-flow设为columnitem-bitem-citem-d以列的方式向下流动:

1
2
3
4
5
6
.container{
display: grid;
grid-template-columns: 60px 60px 60px 60px 60px;
grid-template-rows: 30px 30px;
grid-auto-flow: column;
}

grid

以下属性的简写方式:grid-template-rowsgrid-template-columnsgrid-template-areasgrid-auto-rowsgrid-auto-columnsgrid-auto-flow。它也可以设置grid-column-gapgrid-row-gap为它们的初始值,尽管它们不能通过这个属性来精确设置。
值有:

  • none - 设置所有子属性为它们的初始值
  • <grid-template-rows> / <grid-template-columns> - 分别设置grid-template-rowsgrid-template-columns的指定值,以及设置其他所有子属性为初始值
  • <grid-auto-flow> [<grid-auto-rows> [ / <grid-auto-columns>] ] - 分别接收所有像grid-auto-flowgrid-auto-rowsgrid-auto-columnsaccepts的相同值。如果grid-auto-columns被省略了,那么它的值会通过grid-auto-rows来设置,如果两个都省略了,它们将被设置为默认值。
1
2
3
.container{
grid: none | <grid-template-rows> / <grid-template-columns> | <grid-auto-flow> [<grid-auto-rows> [/ <grid-auto-columns>]];
}

举例:

下面 2 段代码是相等的:

1
2
3
.container{
grid: 200px auto / 1fr auto 1fr;
}
1
2
3
4
5
.container{
grid-template-rows: 200px auto;
grid-template-columns: 1fr auto 1fr;
grid-template-areas: none;
}

下面这 2 段代码也是等价的:

1
2
3
.container{
grid: column 1fr / auto;
}
1
2
3
4
5
.container{
grid-auto-flow: column;
grid-auto-rows: 1fr;
grid-auto-columns: auto;
}

它也可以接收一个更复杂但又相当方便的语法来一次性设置所有属性,你可以指定grid-template-areasgrid-auto-rowsgrid-auto-columns,并且所有其他子属性被设置为它们的默认值。你需要做的是指定网格线的名称和网格轨迹的大小来生成它们的网格区域。最简单的表述方法就是举一个例子:

1
2
3
4
5
.container{
grid: [row1-start] "header header header" 1fr [row1-end]
[row2-start] "footer footer footer" 25px [row2-end]
/ auto 50px auto;
}

上面跟下面是等价的:

1
2
3
4
5
6
.container{
grid-template-areas: "header header header"
"footer footer footer";
grid-template-rows: [row1-start] 1fr [row1-end row2-start] 25px [row2-end];
grid-template-columns: auto 50px auto;
}

网格项的属性

grid-column-start、grid-column-end、grid-row-start 和 grid-row-end

通过参考指定的网格线来决定网格中一个网格项的位置,grid-column-start/grid-row-start是指网格子项开始的线,grid-column-end/grid-row-end是指网格项结束的线。

值有:

  • <line> - 可以是一个数字以适用被标记了数字号的网格线,或者是一个名字以适用命名了的网格线
  • span <number> - 子项将跨越指定数字的网格轨道
  • span <name> - 子项将跨越到指定名字之前的网格线
  • auto - 表示自动布局,自动跨越或者默认跨越一个
1
2
3
4
5
6
.item{
grid-column-start: <number> | <name> | span <number> | span <name> | auto
grid-column-end: <number> | <name> | span <number> | span <name> | auto
grid-row-start: <number> | <name> | span <number> | span <name> | auto
grid-row-end: <number> | <name> | span <number> | span <name> | auto
}

举个例子:

1
2
3
4
5
6
.item-a{
grid-column-start: 2;
grid-column-end: five;
grid-row-start: row1-start
grid-row-end: 3
}
1
2
3
4
5
6
.item-b{
grid-column-start: 1;
grid-column-end: span col4-start;
grid-row-start: 2
grid-row-end: span 2
}

如果grid-column-end/grid-row-end没有生命,网格项将默认跨越一个网格轨道。

网格项可以互相重叠,你可以使用z-index来控制他们的层叠顺序。

grid-column与grid-row

grid-column-start + grid-column-end,和grid-row-start + grid-row-end的简写,分别独立。

值有:

  • / - 每一个属性都可以接收普通模式的值,包括span
1
2
3
4
.item{
grid-column: <start-line> / <end-line> | <start-line> / span <value>;
grid-row: <start-line> / <end-line> | <start-line> / span <value>;
}

举例:

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

如果没有声明结束网格线的值,那么网格子项将默认跨越 1 个网格轨迹。

grid-area

给网格项取一个名字以让它被由grid-template-areas属性创建的模板引用。同时,这个属性也可以用来更简短地表示grid-row-start+ grid-column-start + grid-row-end+ grid-column-end

值有:

  • <name> - 一个你选择的名字
  • <row-start> / <column-start> / <row-end> / <column-end> - 可以是网格线的数字或名字
1
2
3
.item{
grid-area: <name> | <row-start> / <column-start> / <row-end> / <column-end>;
}

举例:

作为分配一个名字给网格项的一种方式:

1
2
3
.item-d{
grid-area: header
}

作为grid-row-start+ grid-column-start + grid-row-end+ grid-column-end的一种简写:

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

justify-self

让网格项的内容以列轴对齐(与之相反align-self是跟行轴对齐),这个值可以应用在单个网格项的内容中。

值有:

  • start - 让内容在网格区域左对齐
  • end - 让内容在网格区域右对齐
  • center - 让内容在网格区域中间对齐
  • stretch - 填充整个网络区域的宽度(默认值)
1
2
3
.item{
justify-self: start | end | center | stretch;
}

举例:

1
2
3
.item-a{
justify-self: start;
}
1
2
3
.item-a{
justify-self: end;
}
1
2
3
.item-a{
justify-self: center;
}
1
2
3
.item-a{
justify-self: stretch;
}

为了让网格中的所有项都对齐,这个行为也可以通过设置网格容器中的justify-items属性来实现。

align-self

让网格项的内容以行轴对齐(与之相反justify-self是跟列轴对齐),这个值可以应用在单个网格项的内容中。

值有:

  • start - 让内容在网格区域上对齐
  • end - 让内容在网格区域下对齐
  • center - 让内容在网格区域中间对齐
  • stretch - 填充着呢个网络区域的高度(默认值)
1
2
3
.item{
align-self: start | end | center | stretch;
}

举例:

1
2
3
.item-a{
align-self: start;
}
1
2
3
.item-a{
align-self: end;
}
1
2
3
.item-a{
align-self: center;
}
1
2
3
.item-a{
align-self: stretch;
}

为了让网格中的所有项都对齐,这个行为也可以通过设置网格容器中的align-items属性来实现。