[译] 理解 CSS 网格布局:网格区域

2,109 阅读10分钟

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

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

我们已经学了如何使用行号和命名网格线定位网格项目。其实还有一种定位网格项目的方式,它是对布局的自然描述——grid-template-areas 属性。本篇文章介绍如何使用 grid-template-areas 属性,以及它的工作原理。

使用 grid-template-areas 描述布局

grid-template-areas 属性接收由一个或多个字符串组成的值。每个字符串(包围在引号中)代表网格里的一行。它可以在已经设置了 grid-template-columns 属性(grid-template-rows  有无设置皆可)的网格上使用。

下例中的网格,用了四个区域描述,每个区域占据两行两列。网格区域是通过在多个单元格重复某个区域名称来划定范围的。

grid-template-areas: "one one two two"
                     "one one two two"
                     "three three four four"
                     "three three four four";

在网格项目上使用 grid-area 属性,为其指定某个区域名称,即表示这个区域被该项目占据了。假设,有一个 .test 项目想要占据叫 one 的这个区域,可以这样指定:

.test {
  grid-area: one;
}

下面是一个完整的例子:

<div class="grid">
  <div class="one">1</div>
  <div class="two">2</div>
  <div class="three">3</div>
  <div class="four">4</div>
</div>

<style>
.grid {
  display: grid;
  grid-template-areas:
    "one one two two"
    "one one two two"
    "three three four four"
    "three three four four";
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: repeat(4, 100px);
}
  
.one {
  grid-area: one;
}

.two {
  grid-area: two;
}

.three {
  grid-area: three;
}

.four {
  grid-area: four;
}
</style>

效果(demo):

image.png

如果使用 Firefox Grid Inspector 查看区域名称和行号分布,会看见每个项目但都占据了两行两列,在网格中得到定位。

image.png

使用 grid-template-areas 的规则

使用 grid-template-areas 属性有一些限定规则,如果打破规则,布局也就无效了。第一个规则是 你必须要完整描述网格,即要考虑网格里的每个单元格。

如果某个单元格需要留空,就必须插入一个或多个点(比如 .... 等都可以)占位。注意在使用多个点时,点与点之间是没有空格的。

所以,还可以这样定义:

grid-template-areas: "one one two two"
                     "one one two two"
                     ". . four four"
                     "three three four four";

效果(demo):

image.png

一个区域名不能在不连续的两块区域上使用。比如,下面的定义 three 的方式就是无效的:

grid-template-areas: "one one three three"
                     "one one two two"
                     "three three four four"
                     "three three four four";

另外,不能创建一个非矩形区域。比如,“L”或“T”形区域的定义是无效的。

grid-template-areas: "one one two two"
                     "one one one one"
                     "three three four four"
                     "three three four four";

格式化字符串

我喜欢用上面的方式来定义 grid-template-areas 属性——每一行字符串对应网格里的一行,看起来很直观。

有时为了达到列与列之间的对齐效果,我会选择使用多个点来指定空单元格。

grid-template-areas: "one   one   two  two"
                     "one   one   two  two"
                     "..... ..... four four"
                     "three three four four";

当然,字符串排列在一行也是有效的:

grid-template-areas: "one one two two" "one one two two" "three three four four" "three three four four";

grid-template-areas 和 grid-area

之所以每块命名区域都要保持为矩形,是因为每块区域都要求能用基于网格线方式实现——而使用四根网格线定义的区域必然是个矩形,不会是个非矩形。

我先举一个使用网格线定位项目的例子:

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

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

效果(demo):

image.png

就是说,只要为一个项目指定了下面四个属性就能准确定位它在网格中的位置了:

  • grid-row-start
  • grid-column-start
  • grid-row-end
  • grid-column-end

grid-area 属性恰好支持以这种顺序指定项目位置的语法:

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

因此,下面的写法:

.grid {
  grid-template-areas: "one   one   two  two"
                       "one   one   two  two"
                       "three three four four";
                       "three three four four";
}

.one {
  grid-area: one;
}

.two {
  grid-area: two;
}

.three {
  grid-area: three;
}

.four {
  grid-area: four;
}

可以改写成:

.one {
  grid-area: 1 / 1 / 3 / 3;
}

.two {
  grid-area: 1 / 3 / 3 / 5;
}

.three {
  grid-area: 3 / 1 / 5 / 3;
}

.four {
  grid-area: 3 / 3 / 5 / 5;
}

grid-area 有趣的地方在于还能够使用行号和命名网格线的方式,为项目指定定位区域。

使用行号的 grid-area

上面讲的是使用 4 个行号来指定 grid-area 属性。但是,如果不是 4 个呢?——比如,我只指定了前三个,没有指定第 4 个值——这时,会使用缺省值 auto,也就是默认延伸一个轨道。因此,如果为一个项目使用的是 grid-row-start: 3,就是说其他三个值都设置成 auto 了——此时项目默认占据一行一列:

.item { grid-area: 3; }

效果:

image.png

使用 indent 的 grid-area

“indent”是对网格中 命名区域 的称呼。

下面我举了一个使用命名网格线指定 grid-area 属性的例子。

.grid {
  display: grid;
  grid-template-columns:
      [one-start three-start] 1fr 1fr
      [one-end three-end two-start four-start] 1fr 1fr [two-end four-end];
  grid-template-rows:
    [one-start two-start] 100px 100px
    [one-end two-end three-start four-start] 100px 100px [three-end four-end];;
}

.two {
  grid-area: two-start / two-start / two-end;
}

注意到,这里我并未指定 grid-column-end 这个值。规范提到,在这种情况下,grid-column-end 的值就复制 grid-column-start 的,而在 grid-column-endgrid-column-start 一样的情况下,grid-column-end 值又会被丢弃,最后的结果与设置行号时一样了——等同于设置了 auto——自动延伸一个轨道。

还有,如果缺失的是第三个属性 grid-row-end 的值,它也是先复制 grid-row-start 的值,最后等同于设置 auto

下面举了一个比较全面的例子,列出了所有的情况:

<div class="grid">
  <div class="one">1</div>
  <div class="two">2</div>
  <div class="three">3</div>
  <div class="four">4</div>
</div>

<style>
.grid {
  display: grid;
  grid-template-columns: 
    [one-start three-start] 1fr 1fr 
    [one-end three-end two-start four-start] 1fr 1fr
    [two-end four-end];
  grid-template-rows: 
    [one-start two-start] 100px 100px
    [one-end two-end three-start four-start] 100px 100px
    [three-end four-end];
}

.one {
  grid-area: one-start / one-start / one-end / one-end;
}

.two {
  grid-area: two-start / two-start / two-end;
}

.three {
  grid-area: three-start / three-start;
}

.four {
  grid-area: four-start;
}
</style>

效果(demo):

image.png

这也能解释了为什么再给 grid-area 仅设置一个 ident 值的情况下也能正常工作的原理(实际拓展成 4 个值的写法了)。

还有一点大家需要知道的是,使用 grid-template-areas 属性创建命名区域的时候,每块区域的边缘网格线都可以使用区域名称引用。我以一个叫 one 的区域名称举例。

下面的写法,

.one {
  grid-area: one;
}

等同于这种(其他三个值复制第一个值):

.one {
  grid-row-start: one;
  grid-row-end: one;
  grid-column-start: one;
  grid-row-end: one;
}

如果是 -start 属性,那么 one 会被解析到这块区域行、列的起始线;如果是 -end 属性,那么 one 就会解析到这块区域行、列的终止线。当然,这种情况仅适应于 grid-area 使用一个命名区域值指定的场景。

在使用 grid-template-areas 的布局中层叠项目

使用 grid-template-areas 定义区域的时候,每个单元格能且只能对应一个名称。当然,在完成主体布局之后,你仍然可以使用行号向布局中叠加新的项目。

下例中,在主题布局之外,我基于网格线在布局中叠加了一个项目:

<div class="grid">
  <div class="one">1</div>
  <div class="two">2</div>
  <div class="three">3</div>
  <div class="four">4</div>
  <div class="five">5</div>
</div>

<style>
.grid {
  display: grid;
  grid-template-areas:
    "one one two two"
    "one one two two"
    "three three four four"
    "three three four four";
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: repeat(4, 100px);
}

.one {
  grid-area: one;
}

.two {
  grid-area: two;
}

.three {
  grid-area: three;
}

.four {
  grid-area: four;
}

/* 与前面不同的是,这个项目是使用行号定位的 */
.five {
  grid-row: 2 / 4;
  grid-column: 2 / 4;
}
</style>

效果(demo):

image.png

你还可以使用命名网格线来指定项目占据的行和列。更好的是,在使用 grid-template-areas 定义网格区域的时候,实际上也会同时给区域周围的四根网格线生成一个以区域名为前缀的名字,区域起始边缘的网格线的名称是 xx-start 的形式,终止边缘的的网格线的名称则是 xx-end 的形式。

以命名区域 one 为例,它的起始边线名字叫 one-start,终止边线的名字则叫 one-end

网格中,你可以使用这些隐式生成的命名网格线定位项目。这在需要不同的断点处重新定义网格布局的场景中,你希望某个定位项目始终位于某个行名之后,会很有用。

<div class="grid">
  <div class="one">1</div>
  <div class="two">2</div>
  <div class="three">3</div>
  <div class="four">4</div>
  <div class="five">5</div>
</div>

<style>
.grid {
  display: grid;
  grid-template-areas:
    "one one two two"
    "one one two two"
    "three three four four"
    "three three four four";
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: repeat(4, 100px);
}
  
.one {
  grid-area: one;
}

.two {
  grid-area: two;
}

.three {
  grid-area: three;
}

.four {
  grid-area: four;
}

.five {
  grid-row: one-start / three-end;
  grid-column: three-start / four-start;
}
</style>

效果(demo):

image.png

在响应式设计中使用 grid-template-areas

我在构建组件库的时候,发使用 grid-template-areas 属性可以很准确的从 CSS 里看到组件组成方式。特别是在不同断点处重新定义网格布局的时候,我只要给 grid-template-areas 属性重新赋值,就能改变当前网格里轨道数量和区域分布。

在下面的 CSS 中,默认组件是单列布局的,在视口宽度达到 600px 以上时,我会重新给 grid-template-area 属性赋值,改变成两列布局。这种方法的好处(也是我前面说过的)就是任何看到这段 CSS 的人都可很清晰看懂是如何布局的。

.wrapper {
  background-color: #fff;
  padding: 1em;
  display: grid;
  gap: 20px;
  grid-template-areas:
    "hd"
    "bd"
    "sd"
    "ft";

}

@media (min-width: 600px) {
  .wrapper {
    grid-template-columns: 3fr 1fr;
    grid-template-areas:
      "hd hd"
      "bd sd"
      "ft ft";
  }
}

header { grid-area: hd; }
article {grid-area: bd; }
aside { grid-area: sd; }
footer { grid-area: ft; }

可访问性

使用 grid-template-areas 和 grid-area 属性定义布局的方式,可能会带来的一个问题就是 元素的视觉呈现跟在源码的顺序可能是不一致的。如果是使用 Tab 按键或语言助手访问的页面,那么看到或听到的内容是按照源码顺序来的,如果布局里的元素因为诗视觉需要移动了位置,那么就会导致视觉呈现上的混乱。所以,在移动项目的时候,一定要留意视觉呈现与源码上的关联性,不至于使用辅助设备访问时,得到不一致的体验。

总结

以上是使用 grid-template-areagrid-area 属性创建布局的全部内容。 如果你还没使用过这种布局方式的话,可以尝试一下。我发现使用这种布局方式设计原型页面非常方便。

(正文完)


广告时间(长期有效)

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

(完)