图解CSS: Grid布局(Part9)

发布于 大漠

前面花了很多篇幅和大家一起探讨了 CSS 网格布局系统中的一些重要概念,并且深入探讨了运用于网格容器上属性的知识和使用。不过我们只围绕着 grid-template-rowsgrid-template-columnsgrid-template-areasgrid-auto-rowsgrid-auto-columnsgrid-ato-flow 展开,对于子网格、瀑布流以及对齐等相关的话题并未展开,留到后面再做探讨。在学习运用于网格容器上属性的时候,在相关的示例中用到了用于网格项目的属性,比如grid-rowgrid-columngrid-area等,并且还多次提到网格项目放置这个概念。或许你对它们的使用有了一个基本的了解,但要彻底掌握他们,我们还是很有必要花更多的时间来学习和探讨。

接下来,我们就开始进入到网格项目的世界中。我们先从放置网格项目开始。

放置网格项目

在 CSS 网格系统中,每个网格项目都与一个网格区域(一个单元格也可以称为是一个网格区域)相关联,即每个网格项目都会放置在四条网格线(两条行网格线和两条列网格线)围绕着的区域,这是一个由网格项目所占据的相邻网格单元格组成的矩形集合。这个网格区域定义了网格项的包含块,其中的自我对齐属性(justify-selfalign-self)决定了它们的实际位置。一个网格项目所占据的单元格也会影响网格的行和列的大小。

这里提到的自我对齐属性 justify-selfalign-self 是用来控制网格项目对齐的属性,这里暂且不对它们进行详细阐述,后面将会花更多时间和大家一起探讨。

一个网格项目的网格区域在网格中的位置是由它的位置定义的,它由一个网格位置和一个网格跨度(网格跨度指的是合并网格单元格)组成。

  • 网格位置(Grid Position): 网格项目在网格中每个轴的位置。网格位置可以是明确指定的,也可以是自动放置的
  • 网格跨度(Grid Span):网格项目在每个轴上占据多少个网格轨道。默认情况之下,在网格系统中,一个网格项目跨度总是确定的,即一个网格单元格,不过我们可以使用其他的方式来确定跨度(即将多个单元格合并成一个)。

如果不希望网格项目自动放置的话,我们可以使用 grid-rowgrid-columngrid-area 等属性来明确指定网格项目在网格系统中的位置。

grid-rowgrid-column 分别是 grid-row-startgrid-row-endgrid-column-startgrid-column-end 的简写属性。

另外,在grid-rowgrid-column 属性上,我们还可以使用关键词 span,用来合并网格单元格。也就是说,我们可以使用下面六个信息中任何一种来明确指定网格项目在网格系统中的位置:

  网格行轨道(Row) 网格列轨道(Column)
起点(Start) 行网格轨道开始的网格线,对应的是 grid-row-start 属性 列网格轨道开始的网格线,对应的是 grid-column-start 属性
终点(End) 行网格轨道结束的网格线,对应的是 grid-row-end 属性 列网格轨道结束的网格线,对应的是 grid-column-end 属性
跨度(Span) 合并行网格轨道上的单元格,即合并行 合并列网格轨道上的单元格,即合并列

网格单元格的跨度,指的是在 grid-rowgrid-column 以及这两者的子属性 grid-row-startgrid-row-endgrid-column-startgrid-column-end 属性之后使用关键词 span [合并单元格的数量]

正如上表所示,在一个给定的维度中(网格的行或列),起点(Start)、终点(End)和跨度(Span)中任何两个的确定值都意味着第三个的确定值。

另外,网格项目的位置和跨度是自动的还是指定的,是有相应条件的:

  网格位置(Grid Position) 网格跨度(Grid Span) 备注
明确指定 至少指定了一条网格线 显式、隐式或默认的跨度 指的是明确放置网格项目或网格项目跨度
自动 没有明确指定的网格线 不适用 指的是自动放置网格项目

自动放置网格项目

默认情况之下,只要是使用了 display 定义了一个网格容器(即值为gridinline-grid),其子元素(包括匿名盒子或伪元素)就会将网格项目放到每个网格单元格中。默认的流向是按行排列网格项目。比如:

.container {
    display: grid; // 或 inline-grid
}

正如示例所示,网格容器未显式使用 grid-template-columnsgrid-template-rowsgrid-template-areas 指定网格轨道 和 grid-auto-flow 指定自动排列方式时。网格容器会根据网格项目的数量来创建多行单列的网格,并且每个网格项目会从第一行依次往下排列:

注意,这是默认的书写模式。如果我们改变书写模式时,自动排列就会有所变化。我们在上面的示例基础上添加writing-mode,来切换不同的书写模式:

尝试着改变示例中 writing-mode 的值,不难发现,当writing-mode 的值为 vertical-rlvertical-lr 时,网格项目会从第一行依次往下排列会变成从第一列(或最后一列)从左(或从右)依次往右(或左)排列:

如果我们显式在网格容器上使用grid-template-columns指定列网格轨道,那么网格项目的自动排列会从第一行与第一列交叉的网格单元格开始,然后按grid-template-columns指定的系列值向右自动放置。如果网格容器第一行无法容纳所有网格项目时,网格项目会自动从第二行开始,并且继续按第一行方式往右自动放置:

.grid__container {
    display: grid;
    grid-template-columns: repeat(2, 1fr 2fr);
}

随着你增加网格项目,网格行网格轨道会越来越多:

注意,上面示例也会受书写模式writing-mode的值影响。

另外,网格项目自动放置还会受grid-auto-flow属性的影响。可以指定网格项目按列来自动放置(即设置grid-auto-flow值为column)。

.grid__container {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: repeat(2, 1fr);
    grid-auto-flow: var(--grid-auto-flow, row);
}

grid-auto-flow 的默认值是 row,这个时候网格项目自动放置是从第一个网格项目开始,从左往右依次排列,网格列轨道不够时,会自动换行;当grid-auto-flow的值设为column时,网格项目自动放置会第一列第一个网格项目从上往下排列,当没有足够多的行网格轨道时会另起一列继续从上往下排列:

其实我们前面专门花了一个章节介绍了 grid-auto-flow 对网格项目自动放置的影响,这里就不再为花篇幅来重复介绍该属性的使用了。

在介绍 grid-auto-flow 时提到过,网格项目的自动放置是有相应的算法的,不过我们并没有详细阐述这个算法。大家不要着急,介绍完明确放置网格项目之后再回过头来聊这个算法,因为聊这些算法对网格项目放置的影响时会涉及到 grid-rowgrid-columngrid-area等属性。因此,深入了解这些属性之后再聊自动放置项目相关的算法更易于理解。

明确放置网格项目

明确放置网格项目指的是在网格项目上显式使用 grid-rowgrid-columngrid-area 引用网格线名称来控制他的位置,其中 grid-area 还可以使用 grid-template-areas 指定的网格区域名称来放置网格项目的位置。我们先来了解这几个属性的基本使用。

grid-row 和 grid-column

grid-rowgrid-column 是简写属性,分别可拆分为:

  • grid-row 分为 grid-row-startgrid-row-end
  • grid-column 分为 grid-column-startgrid-column-end

它们的语法规则如下:

grid-row-start:    <grid-line>
grid-row-end:      <grid-line>
grid-column-start: <grid-line>
grid-column-end:   <grid-line>
grid-row:          <grid-line>[ / <grid-line>]?
grid-column:       <grid-line>[ / <grid-liine>]?

<grid-line> 的值可以是:

<grid-line> = auto | <custom-ident> | [ <integer> && <custom-ident>? ] | [ span && [ <integer> || <custom-ident> ] ]

具体值含义:

  • auto :表示对网格的项目的放置行为不做任何干涉,即自动放置,自动的 span 或者默认 span 值为 1
  • <custom-ident> :如果存在自定义的网格线名(<custom-ident>-start<custom-ident>-end),它就将第一个这样的网格线给网格单元
  • <integer> && <custom-ident>? :将第 n 条网格线给网格单元格放置。如果指定的是负数,则指的是从下边界(右边界)向上边界(左边界)计算的反向顺序。 如果提供的是 <custom-ident>,那么只有以此命名的网格线才会被计算。如果所命名的网格线数超过了网格线数,为了找到该位置,所有隐式的网格线会被假定拥有这个命名
  • span && [<integer>] || <custom-ident>] :为网格单元格定义一个跨度,使得网格单元的网格区域中的一条边界远离另一边界线 n 条网格线。如果提供的是 <custom-ident>,则只有以此命名的网格线才会被计算。如果网格线不足,则假定与搜索方向对应的显式网格一侧的所有隐式网格线都具有该名称

常见的使用方式:

/* 关键词 auto */
grid-row: auto;
grid-row: auto / auto;

/* <custom-ident> 值*/
grid-row: somegridarea;
grid-row: somegridarea / somegridarea;

/* <integer> + <custom-ident> 值 */
grid-row: somegridarea 4;
grid-row: 4 somegridarea / 6;

/* span + <integer> + <custom-ident> 值 */
grid-row: span 3;
grid-row: span somegridarea;
grid-row: 5 somegridarea span;
grid-row: span 3 / 6;
grid-row: span somegridarea / span someothergridarea;
grid-row: 5 somegridarea span / 2 span;

上面示例中的使用方式也适用于grid-column。这里需要特别提出的是,在grid-rowgrid-column 引用网格线时,会有一个 / 分隔符,该符号前面的值表示 *-startgrid-row-startgrid-column-start);符号后面的值表示 *-endgrid-row-endgrid-column-end)。但在grid-row-startgrid-column-startgrid-row-endgrid-column-end 的值中不能使用 / 分隔号。

简单地说:

  • auto :表示什么也不做,网格项目放置的顺序来放置,表现行为有点类似自动放置
  • <grid-line> : 表示网格系统中的网格项目按网格线名称来放置,如果引用了span,则表示网格项目放置在指定位置,并且合并了网格单元格

grid-area

grid-area的语法规则:

grid-area: <grid-line> [ / <grid-line>]{0, 3}

它同时是 grid-row-startgrid-column-startgrid-row-endgrid-column-end 的简写属性,使用 grid-area 也可以通过网格线名称,跨度(span)或 auto来明确放置网格项目。从语法规则中,可以获知,grid-area可以设置1 ~ 4<grid-line>值。

如果grid-area显式指定了4<grid-line>值,那么:

  • 第一个值等同于 grid-row-start 的值
  • 第二个值等同于 grid-column-start 的值
  • 第三个值等同于 grid-row-end 的值
  • 第四个值等同于 grid-column-end 的值

相当于:

grid-area: grid-row-start / grid-column-start / grid-row-end / grid-column-end

注意,每个值之间使用 / 来分隔,而且使用方式和语法值和前面介绍的 grid-row-startgrid-column-startgrid-row-endgrid-column-end 相同。

如果 grid-area 显式指定 3<grid-line> 值,相当于 grid-column-end 值被忽略。这个时候,如果 grid-column-start 是一个自定义的标识(<custom-ident>),则grid-column-end则为该<custom-ident>;否则为 auto

如果 grid-area 显式指定 2<grid-line> 值,相当于设置了 grid-row-start / grid-column-start。如果 grid-column-start 是一个自定义的标识(<custom-ident>),那么grid-column-end也会被设置成这个标识(<custom-ident>);如果grid-row-start是一个自定义的标识(<custom-ident>),那么grid-row-end 也将被设置为该标识(<custom-ident>);否则为auto

如果 grid-area 显式指定 1<grid-line> 值,若 grid-row-start是一个自定义的标识(<custom-ident>),则所有四项普通写法的属性值均为该值;否则为 auto

同样的,grid-area 中也可以使用 span 来合并网格单元格。

grid-area 常见的使用方式:

/* 关键词 auto */
grid-area: auto;
grid-area: auto / auto;
grid-area: auto / auto / auto;
grid-area: auto / auto / auto / auto;

/* <custom-ident> 值 */
grid-area: somegridarea;
grid-area: somegridarea / anothergridarea;

/* <integer> && <custom-ident>? 值 */
grid-area:  4 somegridarea;
grid-area:  4 somegridarea / 2 anothergridarea;

/* span && [ <integer> || <custom-ident> ] 值 */
grid-area: span 3;
grid-area: span 3 / span somegridarea;
grid-area: 2 span / anothergridarea span;

接下来,我们来看具体的使用实例。

基于网格线名称来放置网格项目

在“网格线”一节中我们了解到我们有多种方式给网格线命名,而这些网格线的主要作用就是在 grid-rowgrid-columngrid-area 引用网格线名称来明确指定网格项目。为此,我们先来看如何基于网格线编号来放置网格项目。

我们都知道,只要创建了一个网格系统,就会自动给网格线编号,从1开始为网格线编号。比如下面这个示例:

.grid__container {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-template-columns: repeat(3, 1fr);
}

示例中,使用grid-template-columnsgrid-template-rows 显式创建了一个 3 x 3的网格,使用布局审查器,可以查看到网格系统中每条网格线的数字索引编号:

我们可以在网格项目中使用 grid-row-startgrid-row-endgrid-column-startgrid-column-end 引用网格线数字索引编号来明确放置网格项目,比如:

.grid__item:nth-child(1) {
    grid-row-start: 1;
    grid-row-end: 4;
    grid-column-start: 2;
    grid-column-end: 4;
}

网格项目i1从行网格线编号为1开始,向下延伸到网格线编号4,占三行;同时从列网格线编号2开始,向右延伸到网格线编号4,占两列。即网格项目i1的左上角顶点位于行网格线编号 1和列网格线编号2相交的点;同时右下角顶点位于行网格线编号4和列网格线编号4相交点,它的实际大小是三行两列:

如果我们把上例中行和列网格线的起始编号与终点编号互换,网格项目i1放置的位置还是在同一个位置,比如:

.grid__item:nth-child(1){
    grid-row-start: 4;
    grid-row-end: 1;
    grid-column-start: 4;
    grid-column-end: 2;
}

各网格线位置如下图所示:

我们还可以使用简写的 grid-rowgrid-column 实现同样的效果:

.grid__item:nth-child(1) {
    grid-row: 1 / 4;
    grid-column: 2 / 4;
}

各网格线位置如下图所示:

同样的,在简写的 grid-rowgrid-column 我们也可以将网格起始线和终点编号互换,能达到同样的效果:

.grid__item:nth-child(1) {
    grid-row: 4 / 1;
    grid-column: 4 / 2;
}

各网格线的位置如下图所示:

这几个示例,我们使用不同的方式将网格项目i1放置了同一个位置,而且有一个共同的特点,那就是网格项目i1跨三行两列。在介绍语法规则的时候,提到关键词 span,可以用来合并单元格。那么我们可以使用下面这样的方式来达到同等效果:

.grid__item:nth-child(1) {
    grid-row: 1 / span 3;
    grid-column: 2 / span 2;
}

// 等同于
.grid__item:nth-child(1) {
    grid-row-start: 1;
    grid-row-end: span 3;
    grid-column-start: 2;
    grid-column-end: span 2;
}

各网格线位置如下图所示:

需要注意的是,在grid-rowgrid-column 使用 span 关键词来合并网格单元格,span/ 前后,比如:

.grid__item:nth-child(1) {
    grid-row: 4 / span 3;
    grid-column: 4 / span 2;
}

// 等同于
.grid__item:nth-child(1) {
    grid-row-start: 4;
    grid-row-end: span 3;
    grid-column-start: 4;
    grid-column-end: span 2;
}

grid-row/ 分隔线之前相当于 grid-row-start,分隔线之后相当于 grid-row-end,并且span 3 合并了三行;grid-column/ 分隔线之前相当于 grid-column-start,分隔线之后相当于 grid-column-end,并且 span 2 合并了两列。你看到的效果如下:

其网格线编号示意图如下:

上图中不难发现,相比前面的示例,新增了列网格线编号5和列网格线编号6,新增了行网格线编号567。此时的网格有六条列网格线和七条行网格线,从构建了一个 5 x 6 的网格系统。根据前面所学,我们知道新增的网格线是隐式的网格线,同时新创建的网格也是一个隐式网格。

如果我们希望用用同样的方式实现前面几个示例的效果,即网格项目i1位于行网格线1 ~ 4 和列网格线 2 ~ 4 的区域,我们可以尝试着使用下面这种方式来实现:

.grid__item:nth-child(1) {
    grid-row: -4 / span 3;
    grid-column: -3 / span 2;
}

// 等同于
.grid__item:nth-child(1) {
    grid-row-start: -4;
    grid-row-end: span 3;
    grid-column-start: -3;
    grid-column-end: span 2;
}

网格线的示意图如下:

正如介绍语法规则的时候提到,我们可以在grid-row-startgrid-row-endgrid-column-startgrid-column-end 中使用关键词 span 来指定合并的网格单格单元格:

.grid__item:nth-child(1) {
    grid-row-start: span 3;
    grid-column-start: span 2;
    grid-row-end: span 4;
    grid-column-end: span 3;
}

.grid__item:nth-child(2) {
    grid-row-end: span 2;
    grid-column-end: span 2;
}

.grid__item:nth-child(3) {
    grid-row-start: span 2;
    grid-column-end: span 3;
}

但要注意的是,在grid-row-startgrid-row-endgrid-column-startgrid-column-end 是不能出现/ 分隔符,因为该分隔符只运用于grid-rowgrid-columngrid-area,即:

grid-row    = grid-row-start / grid-row-end
grid-column = grid-column-start / grid-column-end 
grid-area   = grid-row-start / grid-column-start / grid-row-end / grid-column-end

也就是说,在grid-row-startgrid-row-endgrid-columnn-startgrid-column-end 使用 / 是错误的:

/* 语法错误 */
.grid__item:nth-child(4) {
    grid-row-start: 2 / span 2;
    grid-column-start: -2 / span 3;
    grid-row-end: 3 / span 2;
    grid-column-end: -1 / span 2;
}

你或许已经发现了,如果我们在 grid-row-startgrid-row-endgrid-column-startgrid-column-end 中使用 span 合并网格单元格时,无法指定网格项目的起始网格线位置。将会以网格项目所处位置(自动放置时所处的位置)的网格轨道起始线开始,网格项目的终点网格线位置,将会是合并之后所占网格区域最末端网格线。

另外,如果同时给一个网格项目使用关键词span合并网格单元格,比如:

.grid__item:nth-child(1) {
    grid-row-start: span 3;
    grid-column-start: span 2;
    grid-row-end: span 4;
    grid-column-end: span 3;
}

此时,grid-row-endgrid-column-end 将会被忽略:

上面这段代码如果简写的话,相当于:

.grid__item:nth-child(1) {
    grid-row-start: span 3;
    grid-column-start: span 2;
    grid-row-end: span 4;
    grid-column-end: span 3;
}

// 等同于
.grid__item:nth-child(1) {
    grid-row: span 3 / span 4;
    grid-column: span 2 / span 3;
}

网格线示意图如下:

也就是说,如果在明确放置网格项目时希望指定起始位置的话,就不能在 grid-row-startgrid-column-start 中使用 span 关键词。如果又期望合并网格轨道,我们可以采用在 grid-row-startgrid-column-start 上指定网格线编号,作为放置网格项目的起始位置;然后在grid-row-endgrid-column-end 中 使用关键词 span 来指定需要合并的网格单元格。比如:

/* 不推荐这样使用 */
.grid__item:nth-child(1) {
    grid-row: span 2 / span 2;
    grid-column: span 2 / span 2;
}

/* 推荐用法 */
.grid__item:nth-child(2) {
    grid-row: 3 / span 2;
    grid-column: 2 / span 2;
}

不过这种使用方式,很容易让网格跨度超出显式网格区域,从而创建隐式网格线,构建出一个隐式网格:

.grid__item:nth-child(1) {
    grid-row: 3 / span 4;
    grid-column: 2 / span 4;
}

在原 3 x 3 显式网格上新增隐式网格线,重新构建了一个 5 x 6 隐式网格:

在网格容器中,网格线的数字编号有从 1 开始递增的,也有从-1开始递减的,他们方向刚好相反。在ltr(Left-To-Right)的阅读模式之下,行网格线从左向右,从1依次递增,从右向左,从-1依次递减;列网格线从上向下,从1依次递增,从下向上,从-1依次递减。并且无法知道最大的正值,也无法知道最小的负值:

我们在使用 grid-row-startgrid-row-end(它们的简写属性grid-row)和 grid-column-startgrid-column-end(它们的简写属性grid-column)以及 grid-area 来明确放置网格项目时,除了使用正数的数字索引编号的网格线名称之外,还可以使用负数的数字索引编号的网格线。比如:

.grid__item:nth-child(1) {
    grid-row-start: -1;
    grid-row-end: -4;
    grid-column-start: -1;
    grid-column-end: -3;
}

视觉和下面示例效果一致:

.grid__item:nth-child(1) {
    grid-row-start: 1;
    grid-row-end: 4;
    grid-column-start: 2;
    grid-column-end: 4;
}

只不过不同的是网格线的起始和终点位置不同:

但它和下面示例效果完全一致,包括网格线起始和终点位置也相同:

.grid__item:nth-child(1){
    grid-row-start: 4;
    grid-row-end: 1;
    grid-column-start: 4;
    grid-column-end: 2;
}

同样的,也可以使用简写的grid-rowgrid-column达到同样的效果:

.grid__item:nth-child(1) {
    grid-row: -1 / -4;
    grid-column: -1 / -3;
}

grid-row-endgrid-column-endgrid-rowgrid-column中取负数索引号网格线编号时,也可以使用span关键词来合并网格单元格。先来看第一种:

.grid__item:nth-child(1) {
    grid-row: -1 / span 3;
    grid-column: -1 / span 2;
}

// 等同于
.grid__item:nth-child(1) {
    grid-row-start: -1;
    grid-row-end: span 3;
    grid-column-start: -1;
    grid-column-end: span 2;
}

这个示例和下面这个示例效果等同:

.grid__item:nth-child(1) {
    grid-row: 4 / span 3;
    grid-column: 4 / span 2;
}

如果要达到下图这样的效果:

我们需要将grid-row-startgrid-column-start的负数索引网格线编号在-1的基础上减去相应的span 后面的数值(合并网格单元格的数量):

.grid__item:nth-child(1) {
    grid-row: -4 / span 3;
    grid-column: -3 / span 2;
}

前面提到过,grid-area其实相当于 grid-row-startgrid-column-startgrid-row-endgrid-column-end 组合体,并且以 / 来分隔:

grid-area: grid-row-start / grid-column-start / grid-row-end / grid-column-end;

也就是说,前面在grid-row-startgrid-column-startgrid-column-startgrid-column-end引用数字索引号网格线的使用方式,都可以同样的运用于grid-area上面。比如下面这几种使用方式,最终得到的视觉效果是相同的:

.grid__item:nth-child(1) {
    grid-area: 1 / 2 / 4 / 4;
    
    //等同于
    grid-row-start: 1;
    grid-column-start: 2;
    grid-row-end: 4;
    grid-column-end: 4;
}

.grid__item:nth-child(1) {
    grid-area: 4 / 4 / 1 / 2;

    // 等同于
    grid-row-start: 4;
    grid-column-start: 4;
    grid-row-end: 1;
    grid-column-end: 2;
}

.grid__item:nth-child(1) {
    grid-area: -1 / 4 / -4 / 2;

    // 等同于
    grid-row-start: -1;
    grid-column-start: 4;
    grid-row-end: -4;
    grid-column-end: 2;
}

.grid__item:nth-child(1) {
    grid-area: 1 / -3 / 4 / -1;

    // 等同于
    grid-row-start: 1;
    grid-column-start: -3;
    grid-row-end: 4;
    grid-column-end: -1;
}

.grid__item:nth-child(1) {
    grid-area: -1 / -3 / -4 / -1;

    // 等同于
    grid-row-start: -1;
    grid-column-start: -3;
    grid-row-end: -4;
    grid-column-end: -1;
}

虽然取的网格线索引号不一样,但视觉效果是一样的,不同的是起始和终点位置有差异:

就该示例而言,除了上面的这几种组合之外,还有其他的组合,感兴趣的可以尝试着写写。

grid-area除了可以取四个值之外,还可以取三个,两个,甚至是一个值:

  • grid-area只取三个值时,相当于grid-column-end的值为auto
  • grid-area只取两个值时,相当于grid-column-endgrid-row-end 的值都为 auto
  • grid-area只取一个值时,该值是grid-row-start的值,另外三个属性(grid-column-endgrid-row-endgrid-column-start)的值为auto

例如:

.grid_item:nth-child(1) {
    grid-area: 1 / 2 / 4 / 4;

    // 等同于
    grid-row-start: 1;
    grid-column-start: 2;
    grid-row-end: 4;
    grid-column-end: 4;
}

.grid__item:nth-child(1) {
    grid-area: 1 / 2 / 4;

    // 等同于
    grid-row-start: 1;
    grid-column-start: 2;
    grid-row-end: 4;
    grid-column-end: auto;
}

.grid__item:nth-child(1) {
    grid-area: 1 / 2;

    // 等同于
    grid-row-start: 1;
    grid-column-start: 2;
    grid-row-end: auto;
    grid-column-end: auto;
}

.grid__item:nth-child(1) {
    grid-area: 1;

    // 等同于
    grid-row-start: 1;
    grid-column-start: auto;
    grid-row-end: auto;
    grid-column-end: auto;
}

当你调整示例中grid-area不同值时,看到的效果如下图所示:

在使用 grid-area 引用网格线数字索引号放置网格项目,也可以像grid-row-startgrid-column-startgrid-row-endgrid-column-end 一样,使用span 关键词来显式指定合并网格单元格的数量。即:

.grid__item:nth-child(1) {
    grid-area: span 2 / span 2 / span 3 / span 4;
}

// 等同于
.grid__item:nth-child(1) {
    grid-row-start: span 2;
    grid-column-start: span 2;
    grid-row-end: span 3;
    grid-column-end: span 4
}

前面提到过,grid-row-startgrid-column-startgrid-row-endgrid-column-end 中同时使用span来合并网格单元格时,grid-row-endgrid-column-end几乎没有太大的意义:

.grid__item:nth-child(1) {
    grid-row-start: span 2;
    grid-column-start: span 2;
    grid-row-end: span 3;
    grid-column-end: span 4;
}

// 等同于 
.grid__item:nth-child(1) {
    grid-row-start: span 2;
    grid-column-start: span 2;
}

// 也等同于
.grid__item:nth-child(1) {
    grid-row-end: span 3;
    grid-column-end: span 4;
}

因此,在grid-area中使用span时,可以选择性的使用,没有必要同时在所有参数中使用span。当然,在grid-area的四个值中都同时使用span也是可以的,另外可出现的组合会比较多,比如下面这个示例:

在这个示例中,你可能已经发现了:

.grid__item:nth-child(1) {
    grid-area: span 2;
}

// 等同于
.grid__item:nth-child(1) {
    grid-area: 2 span;
}

上面两种情景都等同于:

.grid__item:nth-child(1) {
    grid-row-start: span 2;
    grid-column-start: auto;
    grid-row-end: auto;
    grid-column-end: auto;
}

这个现象让我感到意外,从规范的描述中,我们可以得知,在 CSS 网格系统中使用span 合并网格单元格时,都是按下面的语法规则来定义

<grid-line> =
    auto |
    <custom-ident> |
    [ <integer> && <custom-ident>? ] |
    [ span && [ <integer> || <custom-ident> ] ]

它的意思就是说,每个<grid-line>值可以指定为:

  • 要么是auto关键字
  • <custom-ident>
  • <integer>
  • 或者<custom-ident><integer>,两者之间用空格分隔
  • 或关键词span<custom-ident><integer>或两者一起使用

也就是说,span关键词前面的<integer> 指的是将第 n (这个n指的是网格线数字索引号,可以是正负数,但不能是0)条网格线;span关键词后面的<integer> 指的是合交网格单元格格的数量,它的值默认为1,而且负值和0都是无效的。来看一个简单地示例:

.grid__container {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: repeat(3, 1fr);
    grid-auto-rows: 80px;
    grid-auto-flow: dense;
}

.grid__item:nth-child(1) {
    grid-row-start: span 2;
}

.grid__item:nth-child(2) {
    grid-row-start: 2 span;
}

.grid__item:nth-child(3) {
    grid-row-start: 2 span 2;
}

.grid__item:nth-child(4) {
    grid-row-end: span 2;
}

.grid__item:nth-child(5) {
    grid-row-end: 2 span;
}

.grid__item:nth-child(6) {
    grid-column-start: 2 span;
}

.grid__item:nth-child(7) {
    grid-column-start: span 2;
}

.grid__item:nth-child(8) {
    grid-column-end: 2 span;
}

.grid__item:nth-child(9) {
    grid-column-end: span 2;
}

.grid__item:nth-child(10) {
    grid-area: 2 span;
}

.grid__item:nth-child(11) {
    grid-area: span 2;
}

从示例渲染出来的结果,不难发现:

grid-row-start: span 2;
grid-row-start: 2 span;
grid-row-end:   span 2;
grid-row-end:   2 span;
grid-area:      span 2;
grid-area:      2 span;

上面的代码都是让网格项目以自己所在位置(自动放置)行网格线起始位置向下合并两个单元格。而:

grid-column-start: 2 span;
grid-column-start: span 2;
grid-column-end:   2 span;
grid-column-end:   span 2;

让网格项目以自己所在位置(自动放置)列网格线起始位置向右合并两个单元格。不过grid-row-start: 2 span 2是无效的:

虽然 span <integer><integer> span 在渲染的结果是相同的,但不建议使用 <integer> span 方式来合并网格单元格。

在 CSS 网格系统中,span <integer><integer> span 渲染结果是相似的,但其中原委并不清楚。为此,在使用的时候,建议大家跟着 W3C 规范来使用。

到目前为止,grid-row-startgrid-row-end(或grid-row)和grid-column-startgrid-column-end(或grid-column)和 grid-area 放置网格项目都是使用的显式网格线数字索引编号(是正值或负值)。使用它们的同时还可以使用span关键词来合并网格单元格,不过在这样使用可能会构建一个隐式网格。事实上,除此之外,我们可以使用隐式的网格线数字编号(不在显多网格中定义的网格数字索引号)来放置网格项目。来看一个简单的示例:

.grid__container {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: repeat(3, 1fr);
    grid-auto-rows: 80px;
    grid-auto-columns: 80px;
    grid-auto-flow: dense;
}

上面的代码创建了一个 3 x 3的显式网格,并且隐式网格轨道尺寸是80px,并且自动布局采用的是dense算法:

这个网格中,块轴起始网格线编号是1-4,块轴终点网格线编号是4-1;内联轴起始网格线编号是1-4,内联轴终点网格线编号是4-1。如果在grid-row-startgrid-row-endgrid-column-startgrid-column-endgrid-rowgrid-columngrid-area引用的网格线编写不在显式网格线编号之内,将会创建一个隐式网格。这个我们在介绍隐式网格创建的时候有详细介绍过。比如:

.grid__item:nth-child(1) {
    grid-area: -5 / 5 / 5 / 7; 
}

.grid__item:nth-child(2) {
    grid-row: -6 / 6;
}

.grid__item:nth-child(3) {
    grid-column: -8 / -5;
}

.grid__item:nth-child(4) {
    grid-row: 1 / 4;
    grid-column: 1 / 4;
}

这个时候,3 x 3 的显式网格就变成了一个 10 x 8(十列八行)的隐式网格:

围绕着数字索引号放置网格项目就介绍到这里了。接下来我们来看另一种,即 使用命名的网格线名称(显式命名或隐式命名)放置网格项目。请大家回忆一下,在介绍grid-template-rowsgrid-template-columnsgrid-template-areas时,我们知道这几个属性除了可以定义网格轨道尺寸之外,还可以显式或隐式的给网格线命名。比如:

.grid__container {
    display: grid;
    grid-template-columns: 1fr minmax(300px, 1fr) 1fr;
    grid-template-areas:
        "header header header"
        "sidebar main nav"
        "footer footer footer";
}

上面代码中的grid-template-areas显式定义了网格区域,在此基础上每个网格区域会生成隐式的网格线名称:

我们可以使用 grid-area 引用grid-template-areas中已显式声明的网格区域名称来放置网格项目:

.grid__item:nth-child(1) {
    grid-area: header;

    // 等同于
    grid-row-start: header;
    grid-column-start: header;
    grid-row-end: header;
    grid-column-end: header;
}

.grid__item:nth-child(2) {
    grid-area: sidebar;

    // 等同于
    grid-row-start: sidebar;
    grid-column-start: sidebar;
    grid-row-end: sidebar;
    grid-column-end: sidebar;
}

.grid__item:nth-child(3) {
    grid-area: main;

    // 等同于
    grid-row-start: main;
    grid-column-start: main;
    grid-row-end: main;
    grid-column-end: main;
}

.grid__item:nth-child(4) {
    grid-area: nav;

    // 等同于
    grid-row-start: nav;
    grid-column-start: nav;
    grid-row-end: nav;
    grid-column-end: nav;
}

.grid__item:nth-child(5) {
    grid-area: footer;

    // 等同于
    grid-row-start: footer;
    grid-column-start: footer;
    grid-row-end: footer;
    grid-column-end: footer;
}

grid-area 除了直接引用已声明的网格区域名称放置网格项目之外,还可以网格区域名称创建的显式网格线名称。换成下面这样的代码,同样能实现上例所展示的效果:

.grid__item:nth-child(1) {
    grid-area: header-start / header-start / header-end / header-end;
    
    /** 等同于
    * grid-row-start: header-start;
    * grid-column-start: header-start;
    * grid-row-end: header-end;
    * grid-column-end: header-end;
    ** 或等同于
    * grid-area: header;
    **/
}

.grid__item:nth-child(2) {
    grid-area: sidebar-start / sidebar-start / sidebar-end / sidebar-end;
    
    /** 等同于
    * grid-row-start: sidebar-start;
    * grid-column-start: sidebar-start;
    * grid-row-end: sidebar-end;
    * grid-column-end: sidebar-end;
    ** 或等同于
    * grid-area: sidebar; 
    **/
}

.grid__item:nth-child(3) {
    grid-area: main-start / main-start / main-end / main-end;
    
    /** 等同于
    * grid-row-start: main-start;
    * grid-column-start: main-start;
    * grid-row-end: main-end;
    * grid-column-end: main-end;
    ** 或等同于
    * grid-area: main; 
    **/
}

.grid__item:nth-child(4) {
    grid-area: nav-start / nav-start / nav-end / nav-end;
    
    /** 等同于
    * grid-row-start: nav-start;
    * grid-column-start: nav-start;
    * grid-row-end: nav-end;
    * grid-column-end: nav-end;
    ** 或等同于
    * grid-area: nav; 
    **/ 
}

.grid__item:nth-child(5) {
    grid-area: footer-start / footer-start / footer-end / footer-end;
    
    /** 等同于
    * grid-row-start: footer-start;
    * grid-column-start: footer-start;
    * grid-row-end: footer-end;
    * grid-column-end: footer-end;
    ** 或等同于
    * grid-area: footer; 
    **/
}

而且我们还可以将隐式网格线名称和显式的网格线名称(数字索引号)结合起来使用,同样能达到相同的视觉效果:

.grid__item:nth-child(1) {
    grid-area: header-start / sidebar-start / header-end / nav-end;
}
.grid__item:nth-child(2) {
    grid-area: header-end / sidebar-start / footer-start / main-start;
}
.grid__item:nth-child(3) {
    grid-area: 2 / main-start / -2 / nav-start;
}
.grid__item:nth-child(4) {
    grid-area: header-end / main-end / nav-start / 4;
}
.grid__item:nth-child(5) {
    grid-area: main-end / -4 / footer-end / footer-end;
}

虽然只是grid-template-areas定义的网格区域名称生成的隐式网格线名称,但在grid-area(或 grid-rowgrid-column以及他们的子属性grid-row-startgrid-row-endgrid-column-startgrid-column-end)上可以像引用数字索引号网格线名称的方式来放置网格项目。比如下面这个示例:

.grid__item:nth-child(1) {
    grid-area: header-start / header-start / footer-end / sidebar-end;
}

.grid__item:nth-child(2) {
    grid-area: header-start / main-start / main-end / nav-end;
}

.grid__item:nth-child(3) {
    grid-area: footer-start / footer-start / 5 / main-end;
}

你可能发现了,在上面这个示例中,网格项目i1i3有一个网格单元格重叠在一起:

这也是网格布局强大特性之一,在下一节将会和大家探讨这方面的话题。

接下来再来看 grid-template-rowsgrid-template-columns中定义网格轨道尺寸同时也定义网格线名称,比如:

.grid__container {
    display: grid;
    grid-template-columns: [left] 200px [main-start] 100px [center] 100px 100px [main-end] 50px 1fr [right];
    grid-template-rows: [top title-start] 50px [title-end main-start] 200px [main-end center] 150px 100px [bottom];
    grid-auto-columns: 50px;
    grid-auto-rows: 50px;
}

上面代码创建的网格系统对应的网格线名称如下图所示:

我们可以在 grid-areagrid-row(子属性grid-row-startgrid-row-end)和 grid-column(子属性grid-column-startgrid-column-end)属性上使用grid-template-rowsgrid-template-columns已定义的网格线名称:

.grid__item:nth-child(1) {
    grid-area: main;
    
    /** 等同于
    * grid-row-start: main;
    * grid-column-start: main;
    * grid-row-end: main;
    * grid-column-end: main;
    **/
}

.grid__item:nth-child(2) {
    grid-area: top / left / bottom / main-start;
    
    /** 等同于
    * grid-row-start: top;
    * grid-column-start: left;
    * grid-row-end: bottom;
    * grid-column-end: main-start;
    **/
}

.grid__item:nth-child(3) {
    grid-row: 5 / 4;
    grid-column: center / right;
    
    /** 等同于
    * grid-row-start: 5;
    * grid-row-end: 4;
    * grid-column-start: center;
    * grid-column-end: right;
    **/
}

.grid__item:nth-child(4) {
    grid-column: center;
    grid-row: bottom / span bottom;
    
    /** 等同于
    * grid-column-start: center;
    * grid-column-end: center;
    * grid-row-start: bottom;
    * grid-row-end: span bottom;
    **/
}

你将看到的效果如下:

使用网格审查器,查看相关参数:

前面向大家展示的示例,都是使用已定义的网格线名称来放置网格项目。当然,在使用数字索引号网格线放置网格项目中时介绍过可以使用隐式网格线数字索引号来放置网格项目。同样的,我们也可以使用任何未在grid-template-rowsgrid-template-columnsgrid-template-areas中未显式定义网格线名称来放置网格项目。比如:

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

.grid__item:nth-child(1) {
    grid-column: foo;
}

.grid__item:nth-child(2) {
    grid-column: 2 / span 2 foo;
}

.grid__item:nth-child(3) {
    grid-column: -1 foo;
}

示例中grid-column使用了未显式定义的网格名称foo,但最终也会在网格系统中创建隐式的网格线名称,得到的结果如下图所示:

接着再来看一个示例,使用span来合并单元格,而且是和自己喜欢的网格线名称结合在一起使用,比如span middle。具体的示例代码如下:

.grid__container {
    display: grid;
    grid-template-columns: 200px [middle] 1fr;
    grid-auto-columns: 100px;
}

.grid__item:nth-child(1) {
    grid-column: 3 middle;
}

.grid__item:nth-child(2) {
    grid-column: span 2 middle / 5;
}

.grid__item:nth-child(3) {
    grid-column: -4 / span middle;
}

显式网格和隐式网格的具体参数如下图所示:

从这些示例中我们不难发现,在CSS网格布局中,使用网格线来放置网格项目是非常灵活的。最后来看一个更具综合性的案例:

.grid__container {
    display: grid;
    grid-template-columns: [left] 200px [main-start] 100px [center] 100px 100px [main-end] 50px 1fr [right];
    grid-template-rows: [top title-start] 50px [title-end main-start] 200px [main-end center] 150px 100px [bottom];
    grid-auto-columns: 50px;
    grid-auto-rows: 50px;
}

定义的网格系统如下图所示:

接着像下面这样来放置不同的网格项目:

.grid__item:nth-child(1) {
    grid-column: -8;
    grid-row: -7;
}

.grid__item:nth-child(2) {
    grid-column: span 2 / center;
    grid-row: auto / 1;
}

.grid__item:nth-child(3) {
    grid-column: -1 / span 2;
    grid-row: span 2 / 1;
}

.grid__item:nth-child(4) {
    grid-column: 1 / -1;
    grid-row: title;
}

.grid__item:nth-child(5) {
    grid-area: main;
}

.grid__item:nth-child(6) {
    grid-column: 5 / span right;
    grid-row: auto / center;
}

.grid__item:nth-child(7) {
    grid-column: span bar / 9;
    grid-row: center;
}

.grid__item:nth-child(8) {
    grid-column: span 2 / 2;
    grid-row: -2;
}

.grid__item:nth-child(9) {
    grid-column: center / right;
    grid-row: 5 / 4;
}

.grid__item:nth-child(10) {
    grid-column: center;
    grid-row: bottom / span bottom;
}

该示例网格系统中网格线命名以及网格项目明确放置的位置如下图所示:

通过这些示例,你可能已经感觉到了。在CSS网格系统中,我们可以使用网格线明确的放置网格项目,而且这种方式是非常的灵活,并且姿势非常的多。

那么接下来,我们要一起探讨的是网格项目自动放置的算法。