前端开发者学堂 - fedev.cn

图解CSS: Grid布局(Part8)

发布于 大漠

在 CSS 网格系统中,网格线是一个非常重要的概念。默认情况之下,只要定义了一个网格系统,就会默认创建以数字为索引号的网格线名称(行网格线名称和列网格线名称)。除此之外,还要以在 grid-template-columnsgrid-template-rows 中显式的在中括号[]中给网格线指定名称。通过前面章节的学习,CSS 网格系统中有显式网格和隐式网格之分,同样的,网格线也有显式网格线和隐式网格线之分,位于显式网格上的网格线被称为显式网格线,位于隐式网格上的网格线被称为隐式网格线。而且在网格系统中,明确放置网格项目时离不开网格线,换句话说,网格线命名的好不好直接会影响我们网格项目放置。在这篇文章中将主要和大家探讨网格线怎么命名?感兴趣的请继续往下阅读。

网格线在网格布局系统中是很重要的。前面的内容中虽然没详细的阐述网格布局系统中网格线的相关的知识,但不难发现,网格容器中的 grid-template-columnsgrid-template-rowsgrid-template-areasgrid-auto-columnsgrid-auto-rowsgrid-auto-flow等属性都会创建网格线,并且网格项目上的grid-columngrid-rowgrid-area 可以通过网格线来放置网格项目,而且在创建布局系统时还会带来更多的可能性。接下来,我们将深入探讨论在 CSS 网格布局中命名网格的各种方法,以及由此产生的一些有趣的可能性。

网格线命名

网格布局中的网格线分布在网格轨道的两侧(有水平的也有垂直的),即一条网格线存在于列网格轨道和行网格轨道的两侧。默认情况下,网格线的名称会是数字索引(有正数也有负数),或者开发者显式的指定网格线的命称(指的是字符串命名的网格线名称)。网格线存在的主要作用是开发者可以引用网格线来确定网格项目在网格中的位置。

默认情况下,创建一个网格容器之后,就会创建网格线,即使是没有任何网格项目(包括匿名文本框)。正如下面这个示例所示,在网格容器上显式设置display属性的值为gridinline-grid,在没有任何网格项目下会创建网格线,线格线的命名是数字索引号:

尝试把block容器切换到gridinline-grid,即创建了网格容器,默认就创建了网格线,如下所示:

在这个示例的基础上,我们显式的使用 grid-template-columnsgrid-template-rows 创建一个显式网格:

.grid__container {
    display: grid;
    grid-template-columns: repeat(3, 1fr 2fr);
    grid-template-rows: 20vmin;
    gap: 20px;
}

创建了一个 6 x 1 的网格:

使用浏览器开发者工具审查网格布局,可以看到上面示例中的网格有:

  • 7条列网格线,从左往右的列网格线数字索引号是 1 ~ 7,从右往左的列网格线数字索引号是 -1 ~ -7
  • 2条行网格线,从上往下的行网格线数字索引号是 1 ~ 2,从下往上的行网格线数字索引号是-1 ~ -2

如果你点击示例中的“+”号会新增网格项目,点击“-”号会从网格容器中删除网格项目。在该示例中,如果我们新增网格项目时,会创建新的行网格线的索引号:

咱位稍加改造一下上面这个示例,使用grid-auto-flow来控制新增网格项目自动放置规则:

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

默认情况之下,新添加网格项目时,grid-auto-flowrowdenserow dense会新增行网格线数字索引号;如果grid-auto-flowcolumncolumn dense会新增列网格线数字索引号:

在 CSS 网格系统中,还可以使用 grid-auto-columnsgrid-auto-rows 创建隐式的网格轨道,比如:

.grid__container {
    display: grid;
    gap: 20px;
    grid-auto-columns: 100px;
    grid-auto-rows: 100px;
}

从示例中不难发现,grid-auto-columnnsgrid-auto-rows 有点类似于 grid-template-columnsgrid-template-rows 也能创建数字索引号的网格线名称:

在网格布局系统中,可以使用 grid-rowgrid-columngrid-area 来明确的放置网格项目:

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

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

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

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

除了grid-template-columns之外,grid-columngrid-rowgrid-area 也创建了数字索引号网格线:

这几个示例向大家展示的都是创建数字索引号的网格线方式方法。其实,在 CSS 网格系统中还可以指定网格线的名称。比如:

.grid__container {
    display: grid;
    grid-gap: 20px;
    grid-template-rows: [header-start] 20vh [header-end] ;
    grid-template-columns: [sidebar-start] 1fr 2fr [sidebar-end] 1fr 2fr;
}

这个示例,我们在 grid-template-columnsgrid-template-rows 创建网格轨道的时候,可以在列表中在中括号([])指定网格线名称(字符串)。虽然说是在grid-template-columnsgrid-template-rows 中设置值,但这只是为网格线命名,而不是为网格轨道命名。这个时候,网格容器上的网格线除了有数字索引号的网格线之外,还有在[]指定的网格线名称。如下图所示:

注意,在grid-template-columnsgrid-template-rows 中使用字符串指定网格线名称时,可以使用除 span 之外的任何你喜欢的字符串来命名网格线名称。

指定网格线名称之后,就可以在 grid-columngrid-rowgrid-area上使用已命名好的网格线来放置网格项目(不是数字索引网格线),但他们所起的作用是等同的:

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

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

示例中网格线的名称如下图所示:

在网格轨道列表中显式指定网格线名称时,最好用-start后缀来给网格轨道起始网格线命名(无论是行还是列),用-end后缀来给网格轨道结束网格线命名。正如前面示例中的[header-start][header-end]

很多时候,网格轨道起始和终止网格线名称可以是重合的(相同),因为我们可以给同一网格线命名多个网格线名称。如果你给网格线同时命名多个名称时,可以在[]定义多个名,并且用空格来分隔。比如:

.grid__container {
    display: grid;
    gap: 20px;
    grid-template-columns: [col-start] 1fr [col-end col-start] 1fr [col-start col-end] 1fr [col-end];
}

可以看到字符串命名的网格线名称和自动创建的数字索引号的网格线名称:

除了可以给相同的网格线指定多个不同的网格线名称之外,还可以给不同的网格线指定相同的网格线名称。比如在 repeat() 函数中,使用全名的网格线,就会发生这种情况。比如:

.grid__container {
    display: grid;
    gap: 20px;
    grid-template-columns: repeat(3, [col-a-start] 1fr [col-b-start] 2fr);
}

你会发现示例中的网线的命名是col-a-startcol-b-start交替:

如果多个网格线命名了相同的网格线名称之后,使用grid-rowgrid-columngrid-area 按网格线名称来明确放置网格项目的话。

.grid__item:nth-child(1) {
    grid-column: col-a-start;
}

.grid__item:nth-child(2) {
    grid-column: col-a-start;
}

.grid__item:nth-child(4) {
    grid-column: col-b-start;
}

你会发现:

  • 网格项目i1放置在第一列第一个(第一列与第一行交叉的网格单元格)网格单元格上
  • 网格项目i2放置在第一列第二个(第一列与第二行交叉的网格单元格)网格单元格上
  • 网格项目i4放置在第二列第二个(第二列与第二行交叉的网格单元格)网格单元格上

如果你不希望像上面这样来放置网格项目,比如说网格项目i2放置在第三个col-a-start、网格项目i4放置在第三个col-b-start网格线上(起始位置),只需要在网格线名称之后紧跟网格线对应的索引号,比如:

.grid__item:nth-child(2) {
    grid-column: col-a-start 3;
}

.grid__item:nth-child(4) {
    grid-column: col-b-start 3;
}

对应网格线名称和网格项指定的位置:

除此之外,在使用 grid-template-areas 定义网格区域的时候,也会创建四条网格线名称,并且会有网格区域名称为前缀,后面紧跟-start-end。比如:

body {
    display: grid;
    gap: 10px;
    grid-template-rows: min-content auto min-content;
    grid-template-columns: repeat(12, 1fr);
    grid-template-areas:
        "header header header header header"
        "aside aside main main main"
        "footer footer footer footer footer";
}

header {
    /* ①:  grid-area: header; */
    /* ②: grid-area: header-start / header-start / header-end / header-end; */
    grid-row: header-start / header-end;
    grid-column: header-start / header-end;
}

aside {
    /* ①: grid-area: aside;   */
    /* ②: grid-area: aside-start / aside-start / aside-end / aside-end; */
    grid-row: aside-start / aside-end;
    grid-column: aside-start / aside-end;
}

main {
    /* ①: grid-area: main;   */
    /* ②: grid-area: main-start / main-start / main-end / main-end; */
    grid-rows: main-start / main-end;
    grid-column: main-start / main-end;
}

footer {
    /* ①:  grid-area: footer; */
    /* ②: grid-area: footer-start / footer-start / footer-end / footer-end; */
    grid-rows: footer-start / footer-end;
    grid-column: footer-start / footer-end;
}

section {
    grid-area: header-start / header-end / -1 / -1;
}

这种情况,同一条网格线会有多个网格线名称:

我们可以在grid-columngrid-area 引用网格区域对应的网格线名称来放置网格项目:

header {
    grid-column: header-start / header-end;
}

aside {
    grid-column: aside-start / header-end;
}

main {
    grid-column: header-end / -1;
}

footer {
    grid-column: aside-end / -1;
}

网格线类型

在 CSS 网格系统中,网格有显式网格和隐式网格之分,甚至一个网格同时有两部分组成,一部分称为显式网格,另一部分称为隐式网格。同样的,在 CSS 网格系统中定义网格轨道时创建的网格线也有 显式网格线隐式网格线 之分。

显式网格线

在 CSS 网格系统中,使用 grid-template-columnsgrid-template-rows 属性创建的网格是一个显式网格,在这个显式网格中是网格线被称为显式网格线。简单地说,grid-template-columnsgrid-template-rows 定义的网格列轨道和行轨道,在网格轨道之间会有两条网格线,一条处于网格轨道起始边缘,另一条处于网格轨道结束边缘。

  • 在行网格轨道两侧的网格线,称之为行网格线
  • 在列网格轨道两侧的网格线,称之为列网格线

这些网格线都是用数字来编写的。在块轴(Block Axis)和内联轴(Inline Axis)方向上,网格线的编号都是从起始边缘的 1 开始的。如果书写模式是水平的(writing-mode: horizontal-tb),并且阅读模式是 ltr(LTR,即 Left-To-Right),这意味着 块状方向的网格线编号为1的网格线位于网格的顶部(即行网格线),而内联方向的网格线编号为1的网格线位于网格左边线(即列网格线)。同时在反方向为以-1为起始编号给网格线编号。

比如:

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

这个示例向大家展示了显式网格线及相应网格线编号方式:

如果阅读模式从ltr换成rtl(Right-To-Left),那么块轴方向的第一条网格线还是在顶部,但内联轴方向的第一条网格线就换到网格最右侧了,即正数编号从右往左,以1为递增,负数编号从左往右,以-1为递减:

切换direction的值,可以看到网格线编号的变化:

如果writing-mode的值变成垂直的书写模式,比如vertical-rlvertical-lr。网格器的块轴和内联轴就互换了,列网格线在块轴上,行网格线在内联轴上:

上面我们展示的是grid-template-columnsgrid-template-rows 定义网格轨道创建的默认网格线,即用数字命名网格线名称。如果在这两个属性中使用[<string>]方式给网格线指定命名,也将受writing-mode的影响。

隐式网格线

隐式网格线是位于隐式网格轨道两侧的网格线。简单地说,使用 grid-auto-columnsgrid-auto-rowsgrid-auto-flow 创建的隐式网格上的网格线。比如,使用 grid-auto-rows 创建隐式网格行轨道:

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

示例中的 grid-template-columnsgrid-template-rows 创建了一个显式网格,对应的网格线就是显式网格线:

当你点击“+”按钮新增网格项目时,grid-auto-rows 会创建新的网格线,这些网格线被称为隐式网格线:

正如上图所示,行网格线3和行网格线4是新增的隐式网格线,且按数字索引号方向新增:

  • 在显式网格底部新增行网格轨道,则新增正数索引号网格线
  • 在显式网格顶部栏增行网格轨道,则新增负数索引号网格线

在网格容器上同时使用 grid-auto-columnsgrid-auto-flow:column时,可以像 grid-auto-rows一样,创建隐式的列网格线:

.grid__container {
    display: grid;
    grid-template-columns: repeat(3, 1fr 2fr);
    grid-template-rows: 20vmin;
    grid-auto-columns: 100px;
    grid-auto-flow: column;
}

点击“+”按钮,新增网格项目,将会创建隐式网格列轨道,对应的列网格线就是隐式的列网格线:

新增数字索引号网格线也分:

  • 在显式网格右侧新增列网格轨道,则新增正数索引号网格线
  • 在显式网格左侧新增列网格轨道,则新增负数索引号网格线

除了grid-auto-columnsgrid-auto-rowsgrid-auto-flow 能创建隐式网格之外,还可以使用 grid-rowgrid-columngrid-area 将网格项目放置到显式网格之外的网格单元格上,这个时候也将创建一个隐式网格,新增的隐式网格轨道两侧的网格线也被称为隐式网格线。比如:

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

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

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

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

这个示例在显式网格顶部和底部以及右侧新增了三行三列隐式网格轨道:

  • 网格项目i1使用grid-row: -3 / 3,新增了编号为-3 和编号为3的隐式行网格线
  • 网格项目i2使用grid-column: -3 / span 4,新增了编号为8 和 编号为9的隐式列网格线
  • 网格项目i3使用grid-area: 1 / -2 / 4 / 10,新增了编号为3 和 编号为4的隐式行网格线,同时新增了编号为8、编号为9 和 编号为10的隐式列网格线

在 CSS 网格系统中,还可以使用 grid-template-areas 来创建显式网格,会在网格区域四周创建隐含的网格线名称,并且以-start-end为后缀。比如:

body {
    display: grid;
    gap: 10px;
    grid-template-rows: min-content auto min-content;
    grid-template-columns: repeat(12, 1fr);
    grid-template-areas:
        "header header header header header"
        "aside aside main main main"
        "footer footer footer footer footer";
}

负数索引号网格线

你可能已经发现了,在网格系统中,网格线的数字索引号(不管是行网格线索引号还是列网格线索引号)有负值:

如果在grid-columngrid-rowgrid-area 引用负值索引号放置网格项目时,隐式网格轨道会以相反的方向创建。比如书写模式是 LTR(Left-To-Right)时:

  • 行隐式网格轨道在显式网格上方
  • 列隐式网格轨道在显式网格左侧

实际发生的情况更有趣,而且可能更有用。比如下面这个示例:

.grid__container {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    grid-template-rows: repeat(4, 100px);
}

这里一个 4 x 4 的显式网格,每个轴上有四个显式的网格轨道:

可以使用grid-column 来放置一个网格项目,比如:

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

网格项目i1 从编号为-1的列网格线向右合并三个单元格。当该网格项目内容在一个单元格中可容纳时,它表现的效果如下:

当该网格项目内容在一个单元格中不可容纳时,会扩展到下一个单元格,最终会跨越三个单元格(类似于合并了三个单元格):

正如上面所展示的,网格项目i1 放在了网格的最后网格线上。因为在一个网格容器上,我们是无法知道最后的网格线编号是多少。

简单地说:

使用负的网格线可以让我们把网格项目放在相对于网格的末端

比如:

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

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

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

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

示例中:

  • 网格项目 i1 放置在显式网格的最右侧
  • 网格项目 i2 放置在显式网格的最左侧
  • 网格项目 i3 放置在显式网格的最顶部
  • 网格项目 i4 放置在显式网格的最底部

命名的网格线能做什么?

我猜想,大家应该知道命名的网格线能做什么?是的,正如前面这些示例中展示的那样,我们命名的网格线(不管是显式命名的网格线,还是隐式的命名网格线),他们的主要目的是让网格项目可以按照网格线来明确的放置位置。即,在 grid-rowgrid-columngrid-area 属性上引用网格线名称做为属性值,从而将网格项目放置在相应的网格线范围内。比如,我们使用 grid-template-columnsgrid-template-rows 属性命名列网格线名称:

.grid__container {
    grid-template-columns: [aside-start] 1fr [center-start] 4fr [center-end] 1fr [aside-end];
    grid-template-rows: [top] 1fr [main-start] auto [main-end] 1fr [bottom];
}

在没有设置gap时,网格轨道之间就一条网格线,如果设置该值时,网格轨道之间有两条网格线:

.grid__container {
    grid-template-columns: [aside-start] 1fr [center-start] 4fr [center-end] 1fr [aside-end];
    grid-template-rows: [top] 1fr [main-start] auto [main-end] 1fr [bottom];
    gap: 20px;
}

有了这些网格线名称,可以像下面这样把网格项目放到指定位置(网格线围起来的区域,即网格区域):

.grid__item:nth-child(1) {
    grid-column: aside-start / center-end;
}

.grid__item:nth-child(2) {
    grid-column: center-end / aside-end;
    grid-row: top / bottom;
}

.grid__item:nth-child(3) {
    grid-row: main-start / bottom;
}

.grid__item:last-child {
    grid-column: aside-start / aside-end;
}

具体网格线名称和按网格线名称放置网格项目的结果示意图如下:

上图中字符串网格线名称,比如列网格线aside-startcenter-startcenter-endaside-end 和行网格线topmain-startmain-endbottom 在浏览器开发者工具的网格审查器中并不会显示。目前浏览器开发者工具的网格审查器,只会显示数字索引的网格线名称,比如:

虽然给网格线命名,可以很好的帮助更好的精确放置网格项目,但目前的语法(前面介绍的命名网格线名称的方式)对于很多初学者可能会感到困惑。不过,我们可以换过一种方式来理解,会对大家理解网格线名称命名和使用。

换过一种方式来理解网格线的命名

为了更好的帮助初学者理解网格线命名和网格轨道尺寸的定义,可以像下图来理解:

其中,网格轨道是一列或一行,而行是可选的。造成混乱的原因在于,我们在同时在一行代码中既命名网格线名称,又定义网格轨道的尺寸。比如:

.container {
    grid-template-columns: [aside-start] 1fr [main-start] 1fr [main-end] 1fr [aside-end];
}

我们可以这样来理解:

想象成:

.container {
    grid-template-columns: [网格线名称] 网格轨道尺寸 [网格线名称] 网格轨道尺寸 [网格线名称] 网格轨道尺寸 [网格线名称];
}

同样重要的是,网格线名称可以是除 span 关键词之外任何你喜欢的字符串,但一定要放置在中括号 [] 中。

在显式给网格线命名的时候,是可选的(不必给每一条网格线命名),比如下面这个示例,只给中间的网格线命名了:

.container {
    grid-template-columns: 1fr [main-start] 1fr [main-end] 1fr;
    // 等同
    grid-template-columns: 网格轨道尺寸 [网格线名称] 网格轨道尺寸 [网格线名称] 网格轨道尺寸;
}

在命名网格线名称时,可以在[]中同时以空格 为分隔符,给同一条网格线命多个名称。

.container {
    grid-template-columns: [aside-start] 1fr [aside-end main-start] 1fr [main-end sidebar-start] 1fr [sidebar-end];
    // 等同于
    grid-template-columns: [网格线名称] 网格轨道尺寸 [网格线名称 网格线名称] 网格轨道尺寸 [网格线名称 网格线名称] 网格轨道尺寸 [网格线名称]
}

除了显式命名的网格线名称之外,在网格系统中只要定义了网格,显式声明了网格轨道,就会自动定义以数字为索引号的网格线,这也是默认的网格线名称:

甚至还可以使用表情符来命名网格线名称:

这种方式同样适用于 grid-template-rows 属性上。

简单小结

我们在给网格线命名时,可以:

  • 几乎可以在中括号[]中使用除关键词 span 之外的任何你喜欢的名字,甚至是表情符来命名网格线名称
  • grid-template-areas 创建的网格区域名称,会以-start-end 为后缀创建四条隐式命名的网格线名称
  • 可以同时给网格线命名多个名称,多个名称之间有空格符分隔
  • 多条网格线可以有相同的名字,在引用时在名字后面附上网格线对应的数字编号
  • 在未显式给网络线命名时,将会以数字为网格线编号

在介绍网格线和网格区域,甚至前面很多示例中,我们都使用到了 grid-rowgrid-columngrid-area,并且多次提到了网格项目的放置。那么接下来,就开始进入可用于网格项目的属性世界中,而且会以网格项目放置为入手点。

特别声明,可用于网格容器的属性并没有完全介绍完,后续我们会继续介绍可用到网格容器的属性。这里跳入用于网格项目的属性中是为了更好的帮助大家理解接下来的内容。