[译] 理解 CSS 网格布局:网格线

1,738 阅读13分钟

原文链接:Understanding CSS Grid: Grid Lines,by Rachel Andrew

Photo by Ridham Nagralawala

本文是讲解网格布局系列的第二篇,教你从网格布局新手到专家。

本系列第一篇讲到如何创建网格容器,以及在容器元素上能够使用的属性。网格格式化上下文一旦创建,你也就有了网格线了。有了网格线,你就能在网格项目上添加属性,对项目做定位(place items)了。

读完本篇文章,你将学到:

  • 定位属性:grid-column-startgrid-column-endgrid-row-startgrid-row-end 以及对应的简写属性 grid-column 和 grid-row
  • 如何使用行号(line number)设置 grid-area 属性。
  • 如何根据命名网格线(line name)定位项目。
  • 在定位项目时,显式网格和隐式网格上的表现有何不同。
  • 使用 span 关键字,再讲一点福利内容 subgrid。
  • 当混合使用自动布局(auto-placed)和确定布局(placed)定位项目时,需要注意些什么。

网格线定位

在网格中定位一个项目时,需要先设置它从哪根线开始,到哪儿根线结束。举个例子,我要在一个 5x5 的网格中定位一个项目,让它占据第二列和第三列,第一行到第三行。我会使用下面的 CSS 代码(注意,这里使用的是网格线,而非网格轨道)。

.item {
  grid-column-start: 2;
  grid-column-end: 4;
  grid-row-start: 1;
  grid-row-end: 4;
}

上面的代码还可以简写为以下形式:斜线之前表示起始线(start line),斜线之后表示终止线(end line)。

.item {
  grid-column: 2 / 4;
  grid-row: 1 / 4;
}

效果(demo):

image.png

注意,虽然 .item 的内容很少,但还是占满了整个定位区域。这是因为项目上的对齐属性 align-selfjustify-self 的默认值为 stretch

如果项目只需跨越一个轨道,那么可以忽略设置终止线,因为项目默认就跨域一个轨道。举个例子,设置一个只占据第二列的项目。之前会这么写:

.item {
  grid-column: 2 / 3;
}

其实可以简写成这样的:

.item {
  grid-column: 2;
}

grid-area 属性定位

我们还能用 grid-area 属性定位项目。下一篇文章会全面讲它的用法,不过现在我们只讲使用行号来设置它的方式:

.item {
  grid-area: 1 / 2 / 4 / 4;
}

grid-area 属性行号的设置顺序是这样的:grid-row-startgrid-column-startgrid-row-endgrid-column-end。如果你是在水平、从左到右的语言环境下开发的(比如英语),那么这几个分别对应的方向是 topleftbottomright——跟设置 margin 的方向是相反的——逆时针。

这种设置网格线的方式是为了能适配不同的书写模式(下面会介绍)——先设置起始端,在设置结束端,这与代表物理方向的 topleft... 是不同的。当然,上面这种设置项目位置的方式我不会用,还是会使用 grid-columngrid-row 这两个简写属性,因为它们的可读性更高。

显式网格中的网格线

在一个网格容器中,使用 grid-template-columnsgrid-template-rows 设置的那部分网格区域称为 显式网格。在定义显式区域的同时,还会定义网格线。

这些网格线会编号,起始值是 1,在行内和块方向两个维度上编号。对水平书写模式、从左向右排版的语言来说:行内方向(inline direction)的编号从左开始;块方向(block direction)的编号则从上面开始。

image.png

这里的“行内方向”和“块方向”的概念需要介绍一下:

我们平时在开发网站时,所浏览中、英文网页文本排版方式,基本都是从左到右、从上到下的。我们把> 文字书写方向就叫做行内方向(inline direction),文字折行方向叫做块方向(block direction)。

同样的,如果是垂直书写语言——像中国古籍里排版方式——那么从上到下就是指行内方向,从右到左是块方向。

如果是在水平 RTL(right to left)语言环境下,比如阿拉伯语。此时,块方向仍然从上面开始编号,但行内方向编号就是从右开始的了。

image.png

当然,如果是在垂直书写模式下,比如下图里的网格就设置了 writing-mode: vertical-rl。那么块方向则是从右面开始编号的,行内方向则从上面开始编号。


因此,网格线的编号是跟书写模式、文档或组件的语言环境存在一定关系。

另外,显式网格的最后一根网格线可以使用数值 -1 指代,同理倒数第二第三跟网格线分别是 -2-3,依次类推。假设·,现在有一个项目从网格的第一列横跨到最后一列,那么可以这样写:

.item {
  grid-column: 1 / -1;
}

隐式网格中的网格线

隐形网格的网格线也是从 1 开始编号的。假设我创建一个网格,只显式指定了列(使用 grid-template-columns 属性),并未显式指定行,只使用 grid-auto-rows: 5em 指定了隐式行的尺寸。

在下面的网格布局中,我为一个项目添加了 .placed 类,指定它从第一行跨越到最后一行。

<div class="grid">
  <div>1</div>
  <div>2</div>
  <div>3</div>
  <div>4</div>
  <div>5</div>
  <div>6</div>
  <div class="placed">Placed</div>
</div>

<style>
.grid {
  display: grid;
  grid-template-columns: repeat(5, 100px);
  grid-auto-rows: 5em;
}
.grid > .placed {
  background-color: orange;
  grid-row: 1 / -1; /* the end line is in the implicit grid so -1 does not resolve to it*/
}
</style>


我们来看看结果(demo):

image.png

.placed 的定位结果与我们想象的有出入,按道理应该占两行才对,实际只占了一行。这是因为现在的行轨道没有使用 grid-template-rows 显式创建,导致行号 -1 被解析为 2,而非 3

现在还没有办法定位到隐式网格中的最后一根网格线,因为并不知道一共有多根网格线。

使用命名网格线定位项目

除了使用行号定位项目,我们还你能使用命名网格线来定位项目。一条网格线可以取多个名字,名字包围在方括号 [] 中,网格线名称在各轨道尺寸(tracks sizes)之间定义的。

.grid {
  display: grid;
  grid-template-columns: [full-start] 1fr [main-start] 2fr 2fr [main-end full-end];
}

有了命名网格线,就可以用它来替换默认行号来定位项目。

.item {
  grid-column: main-start / main-end;
}

效果(demo):

image.png

如果一根网格线定义了多个名字,那么你可以任选其一使用。这么多名称最终都是指代同一根网格线。

如何处理多个网格线重名的情况?

这是一个很有趣的场景,多个网格线使用了同一个名字。这会发生在使用了 repeat() 函数的地方。下面例子中,我定义了一个八列网格,是通过 repeat(4, [sm] 1fr [lg] 2fr) 重复四次得到结果。还将较小轨道尺寸左边的网格线命名为 sm,较大轨道尺寸左边的网格线则命名为 lg

这时,如果要指定是具体哪根网格线的时候,就要用到索引了。比如,我想把一个项目从第二根 sm 网格线延伸到第三根 lg 网格线,我会使用 grid-column: sm 2 / lg 3。另外,如果没给网格线指定索引的情况下,默认将解析到第一根叫这个名字的网格线。

<div class="grid">
  <div class="item">Item</div>
</div>

<style>
.grid {
  display: grid;
  grid-template-columns: repeat(4, [sm] 1fr [lg] 2fr);
  grid-template-rows: repeat(5, 50px);
}
.item {
  grid-column: sm 2 / lg 3;
  grid-row: 1 / 4;
}
</style>

效果(demo):

image.png

使用关键字 span

有些情况,只需要一个项目跨越一定数量的轨道,但不知道确切的网格位置。比如,当使用自动定位算法(auto-placement)定位项目的时候,只知道它们跨越了多个轨道,而非默认的就跨越一个。这个时候,就要使用关键字 span 了。

下面举了一个例子, .item1 设置了 grid-column: auto / span 3

<div class="grid">
  <div class="item1">Item 1</div>
  <div class="item2">Item 2</div>
</div>

<style>
.grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  grid-template-rows: repeat(5, 50px);
}
.item1 {
  grid-column: auto / span 3;
}

.item2 {
  grid-column: auto / span 2;
  grid-row: auto / span 2;
}
</style>

来看看效果(demo):

image.png

将来 grid-template-columns 和 grid-template-rows 属性开始全面支持 subgrid 值之后,这种技术将变得非常有用。比如,在卡片布局中,每个卡片包含一个标题和内容区域,我们希望这些卡片里的内容也是彼此对齐的,那么可以通过为卡片设置 grid-template-rows 设置为 subgrid 来控制卡片从父网格中继承(两)行,这样就能实现卡片内容的自动对齐效果了。

<div class="grid">
  <article class="card">
    <h2>This is the heading</h2>
    <p>This is the body of the card.</p>
  </article>
  <article class="card">
    <h2>This is the heading and some headings are bigger</h2>
    <p>This is the body of the card.</p>
  </article>
	<!-- ... -->
</div>

<style>
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
 
 /*
 * 1. `.card` 既是一个网格项目(隶属于 `.grid`),也是一个网格容器。
 * 2. `.card` 作为网格项目占据两行轨道,它在行上的行为表现会传递给作为容器的它的子项目。
 * /
.card {
  grid-row: auto / span 2; /* 2 */
  display: grid; /* 1 */
  grid-template-rows: subgrid; /* 2 */
}
</style>

看下效果(demo。在 Firfox 中查看,Chrome 中目前不支持 subgrid 值):

image.png

基于网格线定位层叠项目

网格系统会自动将项目定位到网格上的空单元格中,而不会遇到项目被定位到同一个单元格中的情况。但是可以基于网格行号、将不同的项目放入同一网格单元中。下例中,我设置了一个跨越两行轨道的图片,还有一个位于第二行轨道的有半透明背景效果的标题文本。

<div class="grid">
  <figure>
    <img src="https://images.unsplash.com/photo-1576451930877-c838b861e9b6?ixlib=rb-1.2.1&q=85&fm=jpg&crop=entropy&cs=srgb&ixid=eyJhcHBfaWQiOjE0NTg5fQ" alt="lights">
    <figcaption>This is the caption</figcaption>
  </figure>
  <!-- ... -->
</div>

<style>
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}
  
figure {
  display: grid;
  grid-template-rows: 300px min-content;
}
  
figure img {  
  object-fit: cover;
  width: 100%;
  height: 100%;
  grid-row: 1 / 3;
  grid-column: 1;
}
  
figcaption {
  grid-row: 2;
  grid-column: 1;
  background-color: rgba(0,0,0,.5);
  color: #fff;
  padding: 10px;
}
</style>

效果(demo):

image.png

这些项目将按照它们在 HTML 中出现的顺序排列。上例中,标题处于图片之后,因此会显示在图片上面。如果标题在前面,那么它就会挡在图片后面,我们就看不见了。另外,如果标题必须在图片前面,那么你可以通过使用 z-index 属性来控制层叠顺序,让图片显示。

混合使用网格线定位与自动定位

如果你混合使用网格线定位与自动定位,需要多加小心。当项目是根据自动定位算法定位的时候,会依次将自己定位到网格上、下一个可用的空白空间。

<div class="grid">
  <figure>...</figure>
  <figure>...</figure>
  <figure>...</figure>
	<!-- ... -->
</div>

<style>
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
}
  
figure {
    margin: 0;
    display: grid;
    grid-template-rows: 200px min-content;
}

figure:nth-child(odd) {
  grid-column: auto / span 2;
}

figure:nth-child(2) {
  grid-column: auto / span 3;
}
</style>

效果(demo):

GIF.gif

网格默认的定位行为是这样的:如果当前剩余的空白空间不足以放得下项目,那么就换行显示,这样就会导致上一行会留下空白。你可以通过设置 grid-auto-flow 值为 dense(该单词为“密集”的意思,意思即尽可能密集的排列项目)来控制这种行为。在这种情况下,如果当前剩下的空白区域足以放得下后续的某个项目,那么这个后续项目会自动挤上来显示,这会导致显示顺序与源码顺序不一致。我们对上例稍作修改,添加一个grid-auto-flow: dense 设置。结果发现项目 3 放在项目 2 之前显示了。

.grid {
  /* ... */
  grid-auto-flow: dense; /* 增加了这一句 */
}

效果(demo):

image.png

请注意,此行为可能会导致用户使用 Tab 键浏览文档时出现问题,因为视顺序与源码顺序不同。自动定位算法会寻找第一个可用的间隙来排布网格项目。打个比方:布局时把头几个网格项目避开顶部区域,留下空白,那么自动定位算法会将后续合适尺寸的项目安排到这些轨道中。

我这里再举一个例子(也是本篇最后一个例子):我们基于行号定位(第 1 项和第 2 项)将第一行的空间空了下来,随后会看到后来的项目会向上移动来填补这些空白空间。

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
}

figure:nth-child(odd) {
  grid-column: auto / span 2;
}

figure:nth-child(1) {
  grid-row: 2;
  grid-column: 1;
}

figure:nth-child(2) {
  grid-row: 2;
  grid-column: 2 / -1;
}

效果(demo):

GIF.gif

看到没?第一、第二个项目被定位到第二行显示了,空下来了第一行,这时后续的第三、第四个项目被自动定位算法推到了第一行。

注意,在动图的最后我还做了一件事情,就是在网格容器上设置了 grid-auto-flow: dense,导致第六个项目位置上移了。这是为了让大家区分自动定位与密集定位:

  • 自动定位在占据空白的同时,会遵守项目顺序——排到空间区域的项目顺序与源码中出现的顺序是一致的。
  • 而密集定位则不管顺序——只要空白空间放得下我,我就要过去。

自动定位算法非常值得我们理解的。这对于理解某些场景下的布局表现会有帮助——比如,如果在网格中新增加了一个项目但没有设置定位区域,则它可能不会像我们所想的那样排在网格的最后面,而是出现在比较靠前的某个“奇怪”位置。

总结

关于网格线的内容还是挺多的。需要记住的是,网格行号从网格创建出来的那一刻起就随之产生,你可以通过指定初始行号和结束行号来定位一个项目。在下一篇文章,我还会介绍另一个定位网格项目的形式:grid-template-areas

(正文完)


广告时间(长期有效)

我有一位好朋友开了一间猫舍,在此帮她宣传一下。现在猫舍里养的都是布偶猫。如果你也是个爱猫人士并且有需要的话,不妨扫一扫她的【闲鱼】二维码。不买也不要紧,看看也行。

(完)