使用网格命名区域可视化(和引用)您的布局

Avatar of Preethi Selvam
Preethi Selvam

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 $200 免费积分!

每当我们使用 CSS Grid 构建简单或复杂的布局时,我们通常会使用行号定位项目。 网格布局包含网格线,这些网格线会自动使用正负行号进行索引(除非我们 显式地命名它们)。 使用行号定位项目是布局的一种好方法,但是 CSS Grid 有多种方法可以以较小的认知负担实现相同的效果。 其中一种方法是我喜欢称为“ASCII”的方法。

简而言之的 ASCII 方法

该方法归结为使用 grid-template-areas 在网格容器级别使用自定义命名区域而不是行号来定位网格项目。

当我们使用 display: grid 将元素声明为网格容器时,网格容器默认会生成一个单列轨道和足以容纳网格项目的行。 容器的参与网格布局的子元素会被转换为网格项目,无论其 display 属性如何。

例如,让我们通过使用 grid-template-columnsgrid-template-rows 属性显式定义列和行来创建一个网格。

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

这段小 CSS 代码片段创建了一个 3×2 网格,其中网格项目在列中占据相等的空间,并且网格包含三个行,轨道大小为 200px

我们可以使用 命名网格区域 来定义整个布局,方法是使用 grid-template-areas 属性。 根据 规范grid-template-areas 的初始值为 none

grid-template-areas = none | <string>+

<string>+ 是列出用引号括起来的字符串组。 每个字符串都表示为一个单元格,每个带引号的字符串都表示为一行。 像这样

grid-template-areas: "head head" "nav main" "foot foot";

grid-template-areas 的值描述布局有四个网格区域。 它们是:

  • head
  • nav
  • main
  • foot

headfoot 跨越两个列轨道和一个行轨道。 其余的 navmain 每个都跨越一个列轨道和一个行轨道。 grid-template-areas 的值很像排列 ASCII 字符,正如 Chris 之前建议的那样,我们可以从 CSS 本身获得对布局整体结构的可视化效果,这是最轻松理解它的方式。

(完整尺寸的 GIF)

好的,所以我们使用四个命名网格区域创建了布局:headnavmainfoot

现在,让我们开始将网格项目定位到命名网格区域,而不是使用行号。 具体来说,让我们将 header 元素放置到名为 head 的命名网格区域中,并在 header 元素中使用 grid-area 属性指定命名网格区域 head

网格布局中的命名网格区域称为 **标识符**。 因此,我们刚才所做的是创建了一个名为 head 的自定义标识符,我们可以使用它将项目放置到某些网格轨道中。

header { grid-area: head; }

我们可以使用其他自定义标识符来使用其他 HTML 元素

nav { grid-area: nav; }
main { grid-area: main; }
footer { grid-area: foot; }

编写命名区域值

根据 CSS 网格布局模块级别 1,所有字符串都必须在以下标记下定义

  • **命名单元格标记:** 这表示网格中的命名网格区域。 例如,head 是一个命名单元格标记。
  • **空单元格标记:** 这表示网格容器中的未命名网格区域。 例如,网格中的空单元格是一个空单元格标记。
  • **废弃标记:** 这是一个语法错误,例如无效的声明。 例如,与网格项目数量相比,单元格和行数量不同会导致声明无效。

grid-template-area 中,每个带引号的字符串(行)都必须具有相同数量的单元格,并且定义完整的网格,而不会忽略任何单元格。

我们可以使用句点字符 (.) 来忽略单元格或将其留为空 **空单元格**。

.grid { 
  display: grid;
  grid-template-areas:
    "head head"
    "nav main"
    "foot .";
}

如果您觉得这在视觉上很奇怪或不平衡,我们可以使用多个句点字符,它们之间没有任何空格。

.grid {
  display: grid;
  grid-template-areas:
    "head head"
    "nav main"
    "foot ....";
}

命名单元格标记可以跨越多个网格单元格,但这些单元格必须形成矩形布局。 换句话说,我们无法创建“L”或“T”形布局,尽管规范确实 暗示将来支持 具有断开区域的非矩形布局。

ASCII 比基于行的放置更好

基于行的放置是我们在其中使用 grid-columngrid-row 属性来定位网格上的元素,方法是使用由数字自动索引的网格线号

.grid-item {
  grid-column: 1 / 3; /* start at grid column line 1 and span to line 3 */
}

但是,如果我们的布局在断点处发生变化,网格项目的行号也会发生变化。 在这些情况下,我们不能依赖我们在特定断点处使用的相同行号。 这就是理解代码需要额外认知负担的地方。

这就是我认为基于 ASCII 的方法最有效的原因。 我们可以使用 grid-template-areas 在网格容器内为每个断点重新定义布局,这为每个断点处的布局提供了方便的视觉效果——就像自文档代码一样!

.grid {
  grid-template-areas:
    "head head"
    "nav main"
    "foot ...."; /* much easier way to see the grid! */
}

.grid-item {
  grid-area: foot; /* much easier to place the item! */
}

我们实际上可以在 DevTools 中看到网格的行号和网格区域。 例如,在 Firefox 中,转到布局面板。 然后,在 **网格** 选项卡下,找到 **“网格显示设置”** 并启用 **“显示行号”** 和 **“显示区域名称”** 选项。

Enabling grid settings.

这种使用命名区域的 ASCII 方法需要更少的努力来可视化和轻松找到元素的放置位置。

Line-based placement versus ASCII Art placement.

让我们看看“通用”用例

每当我在命名网格区域上看到教程时,常见的示例通常是一些包含 headermainsidebarfooter 区域的布局模式。 我喜欢将其视为“通用”用例,因为它涵盖了如此广泛的范围。

The Holy Grail layout in rectangles.

这是一个说明 grid-template-areas 如何工作的很好的例子,但实际应用通常会涉及媒体查询,这些查询设置为在某些视口宽度下更改布局。 我们无需在每个断点处重新声明每个网格项目的 grid-area 来重新定位所有内容,而是可以使用 grid-template-areas 来“响应”断点,并在此过程中获得每个断点处的布局的良好视觉效果!

在定义布局之前,让我们使用 grid-area 属性将标识符分配给每个元素作为基本样式。

header {
  grid-area: head;
}

.left-side {
  grid-area: left;
}

main {
  grid-area: main;
}

.right-side {
  grid-area: right;
}

footer {
  grid-area: foot;
}

现在,让我们再次定义布局作为基本样式。 我们采用移动优先方法,以便默认情况下事物会堆叠

.grid-container {
  display: grid;
  grid-template-areas:
    "head"
    "left"
    "main"
    "right"
    "foot";
}

在这种配置中,每个网格项目都是 **自动大小** 的——这看起来有点奇怪——因此我们可以对网格容器设置 min-height: 100vh 以便我们有更多空间可以操作

.grid-container {
  display: grid;
  grid-template-areas:
    "head"
    "left"
    "main"
    "right"
    "foot";
  min-height: 100vh;
}

现在假设我们希望当视口宽度变宽时,main 元素位于堆叠的 leftright 侧边栏的右侧。 我们使用更新的 ASCII 布局重新声明 grid-template-areas 来实现此目的

@media (min-width: 800px) {
  .parent {
    grid-template-columns: 0.5fr 1fr;
    grid-template-rows: 100px 1fr 1fr 100px;
    grid-template-areas:
      "head head"
      "left main"
      "right main"
      "foot foot";
  }
}

我添加了一些列和行大小,纯粹是为了美观。

随着浏览器变得更宽,我们可能希望再次更改布局,以便 main 位于 leftright 侧边栏之间。 让我们直观地编写布局!

.grid-container {
  grid-template-columns: 200px 1fr 200px; /* again, just for sizing */
  grid-template-areas:
    "head head head"
    "left main right"
    "left main right"
    "foot foot foot";
}

利用隐式线名称以提高灵活性

根据规范,grid-template-areas 会自动为由命名网格区域创建的网格线生成名称。 我们称这些为隐式命名网格线,因为它们是为我们免费命名的,无需任何额外工作。

每个命名的网格区域都会得到四个隐式命名的网格线,两个在列方向,两个在行方向,其中 -start-end 附加到标识符。例如,一个名为 head 的网格区域在两个方向上都会得到 head-starthead-end 线,总共四个隐式命名的网格线。

Implicitly assigned line names.

我们可以利用这些线!例如,如果我们希望一个元素覆盖网格的 mainleftright 区域。之前我们讲过布局必须是矩形的——不允许“T”形和“L”形布局。因此,我们无法使用 ASCII 可视化布局方法来放置覆盖层。但是,我们可以使用与用于定位其他元素相同的 grid-area 属性,使用我们的隐式线名称来放置覆盖层。

你知道 grid-area 是一个简写属性吗?就像 marginpadding 是简写属性一样?它采用多个值的方式相同,但不像 margin 那样遵循“顺时针”方向——它按照 margin-block-startmargin-inline-endmargin-block-endmargin-inline-start 的顺序——grid-area 按照以下方式进行。

grid-area: block-start / inline-start / block-end / inline-end;
Showing the block and inline flow directions in a left-to-right writing mode.

但我们说的是行和列,而不是块和内联方向,对吧?好吧,它们相互对应。行轴对应于块方向,列轴对应于内联方向。

grid-area: grid-row-start / grid-column-start / grid-row-end / grid-column-end;
Block and inline axis.

回到将覆盖元素作为网格项目放置在我们的布局中。grid-area 属性将有助于使用我们的隐式命名网格线来定位元素。

.overlay {
  grid-area: left-start / left-start / right-end / main-end;
}

创建最小网格系统

当我们关注像我们刚刚看到的“通用”用例这样的布局时,很容易将网格区域想象成一个元素一个区域。但它不必那样工作。我们可以重复标识符,为它们在布局中保留更多空间。我们在最后一个示例中看到了重复 headfoot 标识符的情况。

.grid-container {
  grid-template-areas:
    "head head head"
    "left main right"
    "left main right"
    "foot foot foot";
}

请注意,mainleftright 也重复了,但是在块方向上。

让我们忘记整页布局,并在组件上使用命名的网格区域。网格对于组件布局和整页布局一样好!

这是一个非常标准的英雄组件,它包含一行图像,后面跟着不同的文本块。

A row of weightlifting photos above a heading, blurb, then a row of three links.

HTML 很简单。

<div class="hero">
  <div class="image">
    <img src="..." alt="" />
  </div>
  <div class="text">
    <!-- ... -->
  </div>
</div>

我们可以快速进行堆叠布局。

.hero {
  grid-template-areas:
    "image"
    "text";
}

但之后我们必须使用一些 paddingmax-width 或其他东西来使文本区域比图像行更窄。我们通过在两行上重复标识符,将我们的 ASCII 布局扩展成一个四列网格怎么样?

.hero {
  display: grid;
  grid-template-columns: repeat(4, 1fr); /* maintain equal sizing */
  grid-template-areas:
    "image image image image"
    "text  text  text  text";
}

好了,现在我们可以将网格项目放置到这些命名区域中。

.hero .image {
  grid-area: image;
}

.hero .text {
  grid-area: text;
}

到目前为止,一切都很好——两行都占据整个宽度。我们可以将其作为小屏幕的基础布局。

Showing grid lines on the stacked mobile version of the page.

但也许我们希望在视窗达到更大宽度时引入更窄的文本。我们可以使用我们对句号的了解来“跳过”列。在这种情况下,让 text 标识符跳过第一列和最后一列。

@media (min-width: 800px) {
  main {
    grid-template-columns: repeat(6, 1fr); /* increase to six columns */
    grid-template-areas:
      "image image image image image image"
      "..... text  text  text  text  .....";
  }
}

现在我们有了我们想要的间距。

Showing grid lines for a table-sized layout of the page.

如果布局需要在更大的断点处进行额外的调整,我们可以添加更多列,然后继续。

.hero {
  grid-template-columns: repeat(8, 1fr);
  grid-template-areas:
    "image image image image image image image image"
    "..... text  text  text  text  text  text  .....";
}

开发工具可视化

Showing grid lines for a large table sized layout of the page.

还记得 12 列和 16 列布局在 CSS 框架中是大事吗?我们可以快速扩展到那个规模,并在代码中保持一个不错的 ASCII 可视化布局。

main {
  grid-template-columns: repeat(12, 1fr);
  grid-template-areas:
    "image image image image image image image image image image image image"
    "..... text  text  text  text  text  text  text  text  text  text  .....";
}

让我们看看更复杂的东西

我们已经看过了比较通用的示例和比较简单的示例。我们仍然可以使用更复杂的布局获得不错的 ASCII 布局可视化。

让我们逐步实现这个目标。

Three images positioned around a fancy heading.

我已经将它拆分为 HTML 中的两个元素,一个 header 和一个 main

<header>
  <div class="logo"> ... </div>
  <div class="menu"> ... </div>
</header>
<main>
  <div class="image"> ... </div>
  <h2> ... </h2>
  <div class="image"> ... </div>
  <div class="image"> ... </div>
</main>

我认为 flexbox 更适合 header,因为我们可以用这种方式轻松地将其子元素间隔开。所以,这里没有 grid

header {
  display: flex;
  justify-content: space-between;
  /* etc. */
}

grid 非常适合 main 元素的布局。让我们定义布局并将标识符分配给我们需要定位的 .text 和三个 .image 元素的相应元素。我们将从作为小屏幕的基线的这个布局开始。

.grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-areas:
    "image1 image1 .....  image2"
    "texts  texts  texts  texts"
    ".....  image3 image3 .....";
}

你已经看到我们将走向哪里了,对吧?布局在我们面前可视化,我们可以将网格项目放置到具有自定义标识符的位置。

.image:nth-child(1) {
  grid-area: image1;
}

.image:nth-last-child(2) {
  grid-area: image2;
}

.image:nth-last-child(1) {
  grid-area: image3;
}

h2 {
  grid-area: texts;
}
Showing grid lines on a mobile layout of the page.

那是我们的基础布局,所以让我们冒险进入更宽的断点。

@media (min-width: 800px) {
  .grid {
    grid-template-columns: repeat(8, 1fr);
    grid-template-areas:
      ". image1 image1 ...... ......  ...... image2 ."
      ". texts  texts  texts  texts   texts  image2 ."
      ". .....  image3 image3 image3  image3 ...... .";
  }
}

我敢打赌,你确切地知道它会是什么样子,因为布局就在代码中!

Showing grid lines for a table-sized layout of the page.

如果我们决定进一步扩展,也是一样。

.grid {
  grid-template-columns: repeat(12, 1fr);
  grid-template-areas:
    ". image1 image1 .....  .....   .....  .....  .....  .....  .....  .....  ."
    ". texts  texts  texts  texts   texts  texts  texts  texts  texts  image2 ."
    ". .....  image3 image3 image3  image3 .....  .....  .....  .....  .....  .";
}
Showing grid lines for a desktop-sized layout of the page.

这是完整的演示。

我使用“负 margin 黑客”来使第一个图像与标题重叠。

总结

我很好奇,是否还有其他人使用 grid-template-areas 来创建命名区域,以便获得网格布局的 ASCII 可视化效果。将它作为我的 CSS 代码中的参考,帮助我解开了原本可能更加复杂的某些设计,这些设计在处理行号时可能更加复杂。

但至少,用这种方式定义网格布局教会了我们一些关于 CSS 网格的有趣知识,我们在本文中看到了这些知识。

  • grid-template-areas 属性允许我们创建自定义标识符——或“命名区域”——并使用它们来使用 grid-area 属性定位网格项目。
  • grid-template-areas 接受三种类型的“令牌”作为值,包括命名单元格令牌、空单元格令牌和垃圾单元格令牌。
  • grid-template-areas 中定义的每一行都需要相同数量的单元格。忽略单个单元格不会创建布局;它被视为垃圾令牌。
  • 我们可以通过在定义网格布局时,在命名单元格令牌之间使用必需的空格,在 grid-template-areas 属性值中获得网格布局的 ASCII 类可视化图表。
  • 确保空单元格令牌内部没有空格(例如 .....)。否则,空单元格令牌之间的单个空格会创建不必要的空单元格,导致布局无效。
  • 我们可以通过使用 grid-area 重新定位网格项目,然后在网格容器上重新声明 grid-template-areas 来更新轨道列表(如果需要),在不同的断点处重新定义布局。无需触碰网格项目。
  • 自定义命名的网格区域会自动获得四个隐式分配的线名称——<custom-ident>-start<custom-ident>-end,分别位于列和行方向。