Skip to content

网格布局

Flexbox彻底改变了网页布局方式,但这只是开始。
它还有一个大哥:另一个称作网格布局模块(Grid Layout Module)的新规范。
这两个规范提供了一种前所未有的全功能布局引擎。

CSS网格可以定义由行和列组成的二维布局,然后将元素放置到网格中。
有些元素可能只占据网格的一个单元,另一些元素则可能占据多行或多列。
网格的大小既可以精确定义,也可以根据自身内容自动计算。
你既可以将元素精确地放置到网格某个位置,也可以让其在网格内自动定位,填充划分好的区域。使用网格能构建出如图6-1所示的复杂布局。


图6-1 基础网格布局中的盒子

网页布局开启新纪元

网格布局的诞生不像其他CSS特性,比如Flexbox。
浏览器实现Flexbox的早期版本时,加浏览器前缀就能使用。
浏览器前缀的本意是为了让开发人员对某项技术进行试验,并不是直接用于生产环境,然而实际情况并非如此。

Flexbox规范发展了好几年才达到稳定状态。
与此同时,开发人员对这个新特性感到非常兴奋,很早就以前缀方式使用起来。
随着规范的演变,浏览器更新了实现方式。开发人员也必须相应地更新自己的代码,但是还要将以前的代码保留以支持旧版的浏览器。
这导致Flexbox问世的经历十分坎坷。

为了防止历史重演,浏览器厂商采用了全新的方式处理网格布局。
它们不再用浏览器前缀方式,用户必须明确地开启这项特性才能使用。开发人员可以对网格布局进行试验,研究它的工作方式,也可以提交bug。
在普通用户看来,浏览器对网格布局的支持程度为0,但实际上,浏览器几乎完全实现了网格布局。

主流浏览器摒弃了过去那种漫长的开发迭代方式,几乎一夜间推出了功能齐全的、成熟的网格布局。
2017年3月,各大厂商启用了网格布局特性。3周的时间内,Firefox、Chrome、Opera以及Safari全都发布了新版本,启用网格布局。
2017年6月,微软的Edge紧跟步伐。短短3个月,浏览器支持的用户量从0%一跃到近70%。这在CSS世界里可谓史无前例。

网格规范的Level1版本已经稳定,所有现代浏览器都遵守该规范。这意味着现在网格布局已具备在生产环境使用的条件,我们只需要稍微做一些工作以便合理回退。

开启试验特性:

在浏览器默认支持网格布局之前,开发人员可以开启这项特性。虽然现在网格布局已经默认支持,但还是需要知道如何访问其他试验特性,以便将来学习它们。

在Chrome和Opera浏览器中,可以在浏览器设置里打开相应的开关来访问试验特性。
在Chrome中,在地址栏输入 chrome://flags,按下Enter键。在Opera浏览器中,把网址换成 opera://flags
然后滚动到“Experimental Web Platform features”(或者使用浏览器的搜索功能),然后点击开启(Enabled)。

在Firefox中,需要下载和安装Firefox Developer Edition或者Firefox Nightly。
在Safari中,需要安装Safari Technology Preview或者Webkit Nightly Builds版。

构建基础网格

现在,我们先创建一个简单的网格布局,以确认浏览器支持该特性。
你需要将六个盒子放在三列中,如图6-2所示。相应的标记如代码清单6-1所示。


图6-2 由三列两行构成的简单网格

创建一个新网页,给它外链一个新的样式表。将代码清单6-1复制到网页中。
在下面的代码里,我给子元素加上了从a到f的字母,这样能清楚地看到网格里面每个元素的位置。

代码清单6-1 包含六个元素的网格

1
2
3
4
5
6
7
8
<div class="grid">
    <div class="a">a</div>
    <div class="b">b</div>
    <div class="c">c</div>
    <div class="d">d</div>
    <div class="e">e</div>
    <div class="f">f</div>
</div>

跟Flexbox类似,网格布局也是作用于两级的DOM结构。
设置为 display: grid 的元素成为一个网格容器(grid container)。它的子元素则变成网格元素(grid items)。

接下来,要用一些新的属性来定义网格的细节。将代码清单6-2中的样式添加到样式表。

代码清单6-2 基础网格布局

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
.grid {
    /* 将元素设为网格容器 */
    display: grid;
    /* 定义等宽的三列 */
    grid-template-columns: 1fr 1fr 1fr;
    /* 定义登高的两行 */
    grid-template-rows: 1fr 1fr;
    /* 给每个网格的单元格之间加上间隔 */
    grid-gap: 0.5em;
}

.grid > * {
    background-color: darkgray;
    color: white;
    padding: 2em;
    border-radius: 0.5em;
}

在支持网格布局的浏览器中,这段代码会渲染三列,共六个大小相等的盒子(如图6-2所示)。
这里发生了一些新的布局行为。下面将详细介绍。

首先,使用 display: grid 定义一个网格容器。容器会表现得像一个块级元素,100%填充可用宽度。
也可以使用inline-grid(尽管这段代码没写),这样元素就会在行内流动,且宽度只能够包含子元素,不过inline-grid的使用频率不高。

接下来是新属性:grid-template-columnsgrid-template-rows。这两个属性定义了网格每行每列的大小。
本例使用了一种新单位 fr,代表每一列(或每一行)的分数单位(fraction unit)。
这个单位跟 Flexbox 中 flex-grow 因子的表现一样。grid-template-columns: 1fr 1fr 1fr 表示三列等宽。

不一定非得用分数单位,可以使用其他的单位,比如 px、em 或百分数。也可以混搭这几种单位,例如,grid-template-columns: 300px 1fr 定义了一个固定宽度为 300px 的列,后面跟着一个会填满剩余可用空间的列。
2fr的列宽是1fr的两倍。

最后,grid-gap 属性定义了每个网格单元之间的间距。
也可以用两个值分别指定垂直和水平方向的间距(比如 grid-gap: 0.5em 1em)。

可以试试改一下这些值,看会对最终布局产生什么影响。
试试添加新的列或者改变宽度,添加或删除网格元素。
在其他布局里也要继续这样试验,这是掌握新东西最好的方法。

网格剖析

理解网格的各个部分很重要。前面已经提及网格容器和网格元素,这些是网格布局的基本元素。
另外四个重要的概念如图6-3所示。


图6-3 网格的组成部分

  • 网格线(grid line)——网格线构成了网格的框架。一条网格线可以水平或垂直,也可以位于一行或一列的任意一侧。如果指定了grid-gap的话,它就位于网格线上。
  • 网格轨道(grid track)——一个网格轨道是两条相邻网格线之间的空间。网格有水平轨道(行)和垂直轨道(列)。
  • 网格单元(grid cell)——网格上的单个空间,水平和垂直的网格轨道交叉重叠的部分。
  • 网格区域(grid area)——网格上的矩形区域,由一个到多个网格单元组成。该区域位于两条垂直网格线和两条水平网格线之间。

构建网格布局时会涉及这些组成部分。
比如声明 grid-template-columns: 1fr 1fr 1fr 就会定义三个等宽且垂直的网格轨道,同时还定义了四条垂直的网格线:一条在网格最左边,两条在每个网格轨道之间,还有一条在最右边。

前面用 Flexbox 构建了一个网页。现在请回头看看当时的设计,考虑如何用网格来实现。
设计如图6-4所示,虚线标出了每个网格单元的位置。
注意,某些部分跨越了好几个网格单元,也就是填充了更大的网格区域


图6-4 用网格创建的网页布局。虚线标出了每个网格单元的位置

这个网格有两列和四行。前两个水平网格轨道分别是网页标题(Ink)和主导航菜单。
主区域填满了第一个垂直轨道剩下的两个网格单元,两个侧边栏的板块分别放在第二个垂直轨道剩下的两个网格单元里。

说明:
设计不必填满每个网格单元。在想留白的地方让网格单元为空即可。

有一点值得注意的是,使用网格并不会让Flexbox失去用武之地。
在这个网页的布局里面Flexbox仍然是重要部分。我会把应当使用Flexbox的地方指出来。

使用Flexbox布局时,需要按照一定方式嵌套元素。
用网格实现同样的布局需要改一下HTML结构:将嵌套的HTML拉平。
放在网格里的每个元素都必须是主要网格容器的子元素。
新的标记如代码清单6-3所示,按该代码清单所示创建一个新的网页。

代码清单6-3 一个网格布局的HTML结构

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
<!doctype html>
<head>
  <style>
:root {
  box-sizing: border-box;
}

*,
::before,
::after {
  box-sizing: inherit;
}

body {
  background-color: #709b90;
  font-family: Helvetica, Arial, sans-serif;
}

.container {
  display: grid;
  grid-template-columns: 2fr 1fr;
  grid-template-rows: repeat(4, auto);
  grid-gap: 1.5em;
  max-width: 1080px;
  margin: 0 auto;
}

header,
nav {
  grid-column: 1 / 3;
  grid-row: span 1;
}

.main {
  grid-column: 1 / 2;
  grid-row: 3 / 5;
}

.sidebar-top {
  grid-column: 2 / 3;
  grid-row: 3 / 4;
}

.sidebar-bottom {
  grid-column: 2 / 3;
  grid-row: 4 / 5;
}

.tile {
  padding: 1.5em;
  background-color: #fff;
}

.tile > :first-child {
  margin-top: 0;
}

.tile * + * {
  margin-top: 1.5em;
}
  </style>
</head>
<body>
  <div class="container">
    <header>
      <h1 class="page-heading">Ink</h1>
    </header>
    <nav>
      <ul class="site-nav">
        <li><a href="/">Home</a></li>
        <li><a href="/features">Features</a></li>
        <li><a href="/pricing">Pricing</a></li>
        <li><a href="/support">Support</a></li>
        <li class="nav-right">
          <a href="/about">About</a>
        </li>
      </ul>
    </nav>
    <main class="main tile">
      <h1>Team collaboration done right</h1>
      <p>Thousands of teams from all over the
        world turn to <b>Ink</b> to communicate
        and get things done.</p>
    </main>
    <div class="sidebar-top tile">
      <form class="login-form">
        <h3>Login</h3>
        <p>
          <label for="username">Username</label>
          <input id="username" type="text"
            name="username"/>
        </p>
        <p>
          <label for="password">Password</label>
          <input id="password" type="password"
            name="password"/>
        </p>
        <button type="submit">Login</button>
      </form>
    </div>
    <div class="sidebar-bottom tile centered">
      <small>Starting at</small>
      <div class="cost">
        <span class="cost-currency">$</span>
        <span class="cost-dollars">20</span>
        <span class="cost-cents">.00</span>
      </div>
      <a class="cta-button" href="/pricing">
        Sign up
      </a>
    </div>
  </div>
</body>

这个版本的HTML将网页的所有部分都变成了网格元素:头部、菜单(nav)、主区域,还有两个侧边栏。
主区域和两个侧边栏都加上了类tile,用来指定元素共有的白色背景和内边距。

接下来给网页加上网格布局,并将每个部分放到相应的位置。
可以先看一下加上网格后网页大致的形状。(我发现从外到内构建网页更简单。)构建完基础网格,网页会如图6-5所示。


图6-5 用基础网格构建出来的网页

创建一个空的样式表,在网页里外链该样式表,并添加代码清单6-4。

代码清单6-4 使用网格添加最外层的网页布局

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
:root {
  box-sizing: border-box;
}

*,
::before,
::after {
  box-sizing: inherit;
}

body {
  background-color: #709b90;
  font-family: Helvetica, Arial, sans-serif;
}

.container {
  display: grid;
  /* 定义两个垂直的网格轨道 */
  grid-template-columns: 2fr 1fr;
  /* 定义四个水平轨道,大小为 auto */
  grid-template-rows: repeat(4, auto);
  grid-gap: 1.5em;
  max-width: 1080px;
  margin: 0 auto;
}

header,
nav {
    /* 从 1 号垂直网格线跨越到 3 号垂直网格线 */
  grid-column: 1 / 3;
  /* 刚好占据一条水平网格轨道 */
  grid-row: span 1;
}

/* 将其他的网格元素定位到不同的网格线之间 */
.main {
  grid-column: 1 / 2;
  grid-row: 3 / 5;
}

.sidebar-top {
  grid-column: 2 / 3;
  grid-row: 3 / 4;
}

.sidebar-bottom {
  grid-column: 2 / 3;
  grid-row: 4 / 5;
}

.tile {
  padding: 1.5em;
  background-color: #fff;
}

.tile > :first-child {
  margin-top: 0;
}

.tile * + * {
  margin-top: 1.5em;
}

这段代码引入了很多新概念。下面将逐一介绍。

代码首先设置了网格容器,并用 grid-template-columnsgrid-template-rows 定义了网格轨道。
因为列的分数单位分别是 2fr 和 1fr,所以第一列的宽度是第二列的两倍。
定义行的时候用到了一个新方法:repeat() 函数。它在声明多个网格轨道的时候提供了简写方式。

grid-template-rows: repeat(4, auto); 定义了四个水平网格轨道,高度为 auto,这等价于 grid-template-rows: auto auto auto auto。轨道大小设置为auto,轨道会根据自身内容扩展。

用 repeat() 符号还可以定义不同的重复模式,比如 repeat(3, 2fr 1fr) 会重复三遍这个模式,从而定义六个网格轨道,重复的结果是 2fr 1fr 2fr 1fr 2fr 1fr。图6-6展示了最终结果。


图6-6 在模板定义里使用 repeat()方法定义重复模式

还可以将repeat()作为一个更长的模式的一部分。
比如 grid-template-columns: 1fr repeat(3, 3fr) 1fr 定义了一个 1fr 的列,接着是三个 3fr 的列,最后还有一个1fr 的列(可以用 1fr 3fr 3fr 3fr 1fr 表示)。
可以看出来因为展开的写法无法一目了然,所以才产生了 repeat() 这种简写方式。

网格线的编号

网格轨道定义好之后,要将每个网格元素放到特定的位置上。
浏览器给网格里的每个网格线都赋予了编号,如图6-7所示。CSS用这些编号指出每个元素应该摆放的位置。


图6-7 网格线编号从左上角为1开始递增,负数则指向从右下角开始的位置

可以在 grid-columngrid-row 属性中用网格线的编号指定网格元素的位置。
如果想要一个网格元素在垂直方向上跨越1号网格线到3号网格线,就需要给元素设置 grid-column: 1 / 3
或者设置 grid-row: 3 / 5 让元素在水平方向上跨越3号网格线到5号网格线。
这两个属性一起就能指定一个元素应该放置的网格区域。

这些网格元素是按以下代码片段定位的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
.main {
  grid-column: 1 / 2;
  grid-row: 3 / 5;
}

.sidebar-top {
  grid-column: 2 / 3;
  grid-row: 3 / 4;
}

.sidebar-bottom {
  grid-column: 2 / 3;
  grid-row: 4 / 5;
}

这段代码将main元素放在第一列(1号到2号网格线之间),跨越第三行到第四行(3号到5号网格线)的位置。
侧边栏的两个板块放在右列(2号到3号网格线之间),并且在第三行和第四行上下排列。

说明
这些属性实际上是简写属性:grid-columngrid-column-startgrid-column-end 的简写;grid-rowgrid-row-startgrid-row-end 的简写。
中间的斜线只在简写属性里用于区分两个值,斜线前后的空格不作要求。

定位 header 和 nav 的规则集稍有变化。以下代码片段用相同的规则集同时布局这两者。

1
2
3
4
5
header,
nav {
  grid-column: 1 / 3;
  grid-row: span 1;
}

代码里使用之前介绍的grid-column写法,让网格元素占满网格的宽度。
其实还可以用一个特别的关键字span来指定grid-row和grid-column的值(这里用在了grid-row上)。
这个关键字告诉浏览器元素需要占据一个网格轨道。因为这里没有指出具体是哪一行,所以会根据网格元素的布局算法(placement algorithm)自动将其放到合适的位置。
布局算法会将元素放在网格上可以容纳该元素的第一处可用空间,本例中是第一行和第二行。

与 Flexbox 配合

学了网格之后,开发人员经常会问到Flexbox,特别是会问这两种布局方式是否互斥。
当然不会,它们是互补的。二者几乎是一起开发出来的,虽然它们的功能有一些重叠的地方,但是它们各自擅长的场景不一样。
在一个设计场景里,要根据特定的需求来做出选择。这两种布局方式有以下两个重要区别。

  • Flexbox本质上是一维的,而网格是二维的。
  • Flexbox是以内容为切入点由内向外工作的,而网格是以布局为切入点从外向内工作的。

因为Flexbox是一维的,所以它很适合用在相似的元素组成的行(或列)上。
它支持用 flex-wrap 换行,但是没法让上一行元素跟下一行元素对齐。相反,网格是二维的,旨在解决一个轨道的元素跟另一个轨道的元素对齐的问题。它们的区别如图6-8所示。


图6-8 Flexbox在一个方向上对齐元素,而网格在两个方向上对齐元素

按照CSS WG的成员Rachel Andrew的说法,它们的第二个区别在于,Flexbox以内容为切入点由内向外工作,而网格以布局为切入点由外向内工作。
Flexbox让你在一行或一列中安排一系列元素,但是它们的大小不需要明确指定,每个元素占据的大小根据自身的内容决定。

而在网格中,首先要描述布局,然后将元素放在布局结构中去。
虽然每个网格元素的内容都能影响其网格轨道的大小,但是这同时也会影响整个轨道的大小,进而影响这个轨道里的其他网格元素的大小。

用网格给网页的主区域定位是因为我们希望内容能限制在它所在的网格内,但是对于网页上的其他元素,比如导航菜单,则允许内容对布局有更大的影响。
也就是说,文字多的元素可以宽一些,文字少的元素则可以窄一些。同时这还是一个水平(一维)布局。
因此,用 Flexbox 来处理这些元素更合适。接下来用 Flexbox 给这些元素布局,完成整个页面。

如图6-9所示,顶部导航菜单里的链接是水平对齐的。
同时用Flexbox实现右下角的价格的样式。加上这些布局和少量其他样式后,网页的最终样式就完成了。


图6-9 完成的网页样式

代码清单6-5 网页

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
<!doctype html>
<head>
  <style>
  :root {
    box-sizing: border-box;
  }

  *,
  ::before,
  ::after {
    box-sizing: inherit;
  }

  body {
    background-color: #709b90;
    font-family: Helvetica, Arial, sans-serif;
  }

  .container {
    display: grid;
    grid-template-columns: 2fr 1fr;
    grid-template-rows: repeat(4, auto);
    grid-gap: 1.5em;
    max-width: 1080px;
    margin: 0 auto;
  }

  header,
  nav {
    grid-column: 1 / 3;
    grid-row: span 1;
  }

  .main {
    grid-column: 1 / 2;
    grid-row: 3 / 5;
  }

  .sidebar-top {
    grid-column: 2 / 3;
    grid-row: 3 / 4;
  }

  .sidebar-bottom {
    grid-column: 2 / 3;
    grid-row: 4 / 5;
  }

  .tile {
    padding: 1.5em;
    background-color: #fff;
  }

  .tile > :first-child {
    margin-top: 0;
  }

  .tile * + * {
    margin-top: 1.5em;
  }

  .page-heading {
    margin: 0;
  }

  .site-nav {
    display: flex;
    margin: 0;
    padding: .5em;
    background-color: #5f4b44;
    list-style-type: none;
    border-radius: .2em;
  }

  .site-nav > li {
    margin-top: 0;
  }

  .site-nav > li > a {
    display: block;
    padding: .5em 1em;
    background-color: #cc6b5a;
    color: white;
    text-decoration: none;
  }

  .site-nav > li + li {
    margin-left: 1.5em;
  }

  .site-nav > .nav-right {
    margin-left: auto;
  }

  .login-form h3 {
    margin: 0;
    font-size: .9em;
    font-weight: bold;
    text-align: right;
    text-transform: uppercase;
  }

  .login-form input:not([type=checkbox]):not([type=radio]) {
    display: block;
    margin-top: 0;
    width: 100%;
  }

  .login-form button {
    margin-top: 1em;
    border: 1px solid #cc6b5a;
    background-color: white;
    padding: .5em 1em;
    cursor: pointer;
  }

  .centered {
    text-align: center;
  }

  .cost {
    display: flex;
    justify-content: center;
    align-items: center;
    line-height: .7;
  }

  .cost > span {
    margin-top: 0;
  }

  .cost-currency {
    font-size: 2rem;
  }
  .cost-dollars {
    font-size: 4rem;
  }
  .cost-cents {
    font-size: 1.5rem;
    align-self: flex-start;
  }

  .cta-button {
    display: block;
    background-color: #cc6b5a;
    color: white;
    padding: .5em 1em;
    text-decoration: none;
  }
  </style>
</head>
<body>
  <div class="container">
    <header>
      <h1 class="page-heading">Ink</h1>
    </header>
    <nav>
      <ul class="site-nav">
        <li><a href="/">Home</a></li>
        <li><a href="/features">Features</a></li>
        <li><a href="/pricing">Pricing</a></li>
        <li><a href="/support">Support</a></li>
        <li class="nav-right">
          <a href="/about">About</a>
        </li>
      </ul>
    </nav>
    <main class="main tile">
      <h1>Team collaboration done right</h1>
      <p>Thousands of teams from all over the
        world turn to <b>Ink</b> to communicate
        and get things done.</p>
    </main>
    <div class="sidebar-top tile">
      <form class="login-form">
        <h3>Login</h3>
        <p>
          <label for="username">Username</label>
          <input id="username" type="text"
            name="username"/>
        </p>
        <p>
          <label for="password">Password</label>
          <input id="password" type="password"
            name="password"/>
        </p>
        <button type="submit">Login</button>
      </form>
    </div>
    <div class="sidebar-bottom tile centered">
      <small>Starting at</small>
      <div class="cost">
        <span class="cost-currency">$</span>
        <span class="cost-dollars">20</span>
        <span class="cost-cents">.00</span>
      </div>
      <a class="cta-button" href="/pricing">
        Sign up
      </a>
    </div>
  </div>
</body>

当设计要求元素在两个维度上都对齐时,使用网格。当只关心一维的元素排列时,使用Flexbox。
在实践中,这通常(并非总是)意味着网格更适合用于整体的网页布局,而Flexbox更适合对网格区域内的特定元素布局。
继续用网格和Flexbox,你就会对不同情况下该用哪种布局方式得心应手。

替代语法

布局网格元素还有另外两个替代语法:命名的网格线和命名的网格区域。至于选择哪个纯属个人偏好。
在某些设计中,一种语法会比另一种语法更好理解。下面分别介绍这两个语法。

命名的网格线

有时候记录所有网格线的编号实在太麻烦了,尤其是在处理很多网格轨道时。
为了能简单点,可以给网格线命名,并在布局时使用网格线的名称而不是编号。
声明网格轨道时,可以在中括号内写上网格线的名称,如下代码片段所示。

1
grid-template-columns: [start] 2fr [center] 1fr [end];

这条声明定义了两列的网格,三条垂直的网格线分别叫作start、center和end。
之后定义网格元素在网格中的位置时,可以不用编号而是用这些名称来声明,如下代码所示。

1
grid-column: start / center;

这条声明将网格元素放在1号网格线(start)到2号网格线(center)之间的区域。
还可以给同一个网格线提供多个名称,比如下面的声明(为了可读性,这里将代码换行了)。

1
2
3
grid-template-columns: [left-start] 2fr
                       [left-end right-start] 1fr
                       [right-end];

在这条声明里,2号网格线既叫作 left-end 也叫作 right-start,之后可以任选一个名称使用。
这里还有一个彩蛋:将网格线命名为 left-startleft-end,就定义了一个叫作 left 的区域,这个区域覆盖两个网格线之间的区域。
-start-end 后缀作为关键字,定义了两者之间的区域。
如果给元素设置 grid-column: left,它就会跨越从 left-startleft-end 的区域。

代码清单6-6使用命名的网格实现网页布局。它的效果跟代码清单6-4一样。用代码清单6-6更新样式表。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
.container {
    display: grid;
    /* 给每个垂直的网格线命名 */
    grid-template-columns: [left-start] 2fr
                           [left-end right-start] 1fr
                           [right-end];
    /* 将水平网格线命名为 "row" */
    grid-template-rows: repeat(4, [row] auto);
    grid-gap: 1.5em;
    max-width: 1080px;
    margin: 0 auto;
  }

  header,
  nav {
    grid-column: left-start / right-end;
    grid-row: span 1;
  }

  .main {
      /* 跨越 left-start 到 left-end 之间的区域 */
    grid-column: left;
    /* 从第三行网格线开始放置元素,跨越两个网格轨道 */
    grid-row: row 3 / span 2;
  }

  /* 跨越 right-start 到 right-end 的区域 */
  .sidebar-top {
    grid-column: right;
    grid-row: 3 / 4;
  }

  .sidebar-bottom {
    grid-column: right;
    grid-row: 4 / 5;
  }

这个例子用命名的网格线将每个元素放在的相应网格列上,并且在repeat()里声明了一条命名的水平网格线,于是每条水平网格线被命名为row(除了最后一条)。
这看起来很不可思议,但是重复使用同一个名称完全合法。
然后将main元素放在从row 3(第三个叫row的网格线)开始的地方,并跨越两个网格轨道。

可以以各种方式命名的网格线。它们在网格里的用法也是五花八门,这取决于每个网格特定的结构,比如可以实现如图6-10所示的布局。


图6-10 将网格元素放在第二个“col”网格线处,跨越两个轨道(col 2 / span 2)

这个场景展示了一种重复模式:每两个网格列为一组,在每组的两个网格轨道之前命名一条网格线(grid-template-columns: repeat(3, [col] 1fr 1fr))。
然后就可以借助命名的网格线将一个元素定位到第二组网格列上(grid-column: col 2 / span 2)。