每当我们使用CSS Grid构建简单或复杂的布局时,我们通常会用行号来定位项目。网格布局包含的网格线会自动以正负行号作为索引(除非我们明确地命名它们)。用行号来定位项目是一种很好的布局方式,尽管CSS Grid有许多方法来完成同样的工作,但认知上的负担不大。其中一种方法是我认为的 "ASCII "方法。
ASCII方法简而言之
这个方法可以归结为使用grid-template-areas
,在网格容器层面使用自定义命名的区域来定位网格项目,而不是行数。
当我们使用display: grid
声明一个元素为网格容器时,默认情况下,网格容器会生成一个单列轨道和足以容纳网格项目的行。容器中参与网格布局的子元素被转换为网格项,与它们的display
属性无关。
例如,让我们创建一个网格,通过使用 "和 "来明确定义列和行。 grid-template-columns
和 grid-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
head
和 ,横跨两个列轨和一个行轨。剩下的 和 各跨一个列轨道和一个行轨道。 的值很像排列 ASCII 字符,正如foot
nav
main
grid-template-areas
Chris前段时间建议的那样,我们可以从 CSS 本身获得布局的整体结构的可视化,这是最省事的理解方式。
好了,我们用四个命名的网格区域创建了我们的布局。head
,nav
,main
,foot
。
现在,让我们开始根据命名的网格区域来定位网格项目,而不是行号。具体来说,让我们把一个header
元素放入命名的网格区域head
,并在header
元素中使用grid-area
属性指定命名的网格区域head
。
网格布局中命名的网格区域被称为idents。所以,我们刚才所做的是创建了一个名为head
的自定义标识,我们可以用它来将项目放入某些网格轨道。
header { grid-area: head; }
我们可以使用其他的自定义idents的HTML元素。
nav { grid-area: nav; }
main { grid-area: main; }
footer { grid-area: foot; }
编写命名的区域值
根据CSS Grid Layout Module Level 1,所有字符串都必须定义在以下标记下:
- 命名的单元格令牌:这代表网格中的命名网格区域。例如,
head
是一个命名的单元格令牌。 - 无名单元格令牌:这代表网格容器中未命名的网格区域。例如,网格中的空单元格就是一个空单元格令牌。
- Trash token:这是一个语法错误,比如说无效的声明。例如,与网格项目的数量相比,单元格和行的数量悬殊会使声明无效。
在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-column
和grid-row
属性,使用网格线号在网格上定位一个元素,网格线号是由一个数字自动索引的:
.grid-item {
grid-column: 1 / 3; /* start at grid column line 1 and span to line 3 */
}
但是如果我们的布局在断点处发生变化,网格项目的行号就会发生变化。在这些情况下,我们不可能依赖我们在特定断点时使用的相同行号。这就是需要额外的认知负担来理解代码的地方。
这就是为什么我认为基于ASCII的方法效果最好。我们可以在网格容器中使用grid-template-areas
,为每个断点重新定义布局,这为布局在CSS中的直接表现提供了方便的视觉效果--这就像自带文档的代码一样
.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中,进入Layout面板。然后,在网格标签下,找到"网格显示设置 "并启用"显示行号 "和"显示区域名称 "选项:
这种使用命名区域的ASCII方法需要花费较少的精力来可视化并轻松找到元素的位置:
让我们来看看 "通用 "的用例
每当我看到关于命名网格区域的教程时,常见的例子一般是一些包含header
,main
,sidebar
, 和footer
区域的布局模式。我喜欢把它看作是 "通用 "的用例,因为它涵盖了如此广泛的范围。
这是一个很好的例子,可以说明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
元素位于堆叠的left
和right
侧边栏的右边。我们用一个更新的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
被夹在left
和right
的侧边栏之间。让我们直观地写出布局!
.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";
}
CodePen 嵌入回退
利用隐式行名的灵活性
根据规范,grid-template-areas
为命名的网格区域所创建的网格线自动生成名称。我们称这些为隐式命名的网格线,因为它们是免费为我们命名的,没有任何额外工作。
每个被命名的网格区域都有四条隐式命名的网格线,在列方向有两条,在行方向有两条,其中-start
和-end
是附加在标识上的。例如,一个名为head
的网格区域在两个方向上都有head-start
和head-end
线,总共有四条隐式命名的网格线。
我们可以利用这些线来发挥我们的优势!例如,如果我们想让一个元素覆盖我们网格中的main
,left
, 和right
区域。早些时候,我们谈到了布局必须是矩形的--不允许有 "T "和 "L "型的布局。因此,我们无法使用ASCII视觉布局方法来放置覆盖物。然而,我们可以使用我们的隐含行名,在覆盖层上使用我们用来定位其他元素的相同的grid-area
属性。
你知道吗,grid-area
是一个速记属性,与margin
和padding
是速记属性的方式相同?它以同样的方式取多个值,但不是像margin
那样按照 "顺时针 "方向--即按照margin-block-start
,margin-inline-end
,margin-block-end
, 和margin-inline-start
的顺序--grid-area
是这样的。
grid-area: block-start / inline-start / block-end / inline-end;
但我们讨论的是行和列,而不是块和内联方向,对吗?嗯,它们是相互对应的。行轴对应的是块状方向,列轴对应的是内联方向。
grid-area: grid-row-start / grid-column-start / grid-row-end / grid-column-end;
回到将覆盖元素定位为我们布局中的一个网格项目。grid-area
属性将有助于使用我们隐式命名的网格线来定位该元素。
.overlay {
grid-area: left-start / left-start / right-end / main-end;
}
CodePen嵌入回退
创建一个最小的网格系统
当我们专注于像刚才看到的 "通用 "用例的布局时,很容易想到每个元素有一个区域的网格区域。但它不一定要这样做。我们可以重复idents,在布局中为它们保留更多的空间。当我们在上一个例子中重复head
和foot
的idents时,我们就看到了这一点。
.grid-container {
grid-template-areas:
"head head head"
"left main right"
"left main right"
"foot foot foot";
}
请注意,main
,left
, 和right
也是重复的,但是在块的方向。
让我们忘记整个页面的布局,在一个组件上使用命名的网格区域。网格对于组件的布局和整页的布局一样好!
这里有一个非常标准的英雄组件,它有一排图片,后面是不同的文本块。
这个HTML很简单。
<div class="hero">
<div class="image">
<img src="..." alt="" />
</div>
<div class="text">
<!-- ... -->
</div>
</div>
我们可以这样做来实现真正的快速堆叠布局。
.hero {
grid-template-areas:
"image"
"text";
}
但是,我们必须使用一些padding
,max-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;
}
到目前为止,一切都很好--两行都占据了整个宽度。我们可以把它作为我们小屏幕的基本布局。
但也许我们想在视口达到较大宽度时引入较窄的文本。我们可以利用我们对句号字符的了解来 "跳过 "列。在这种情况下,让text
ident跳过第一和最后一列。
@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 .....";
}
}
现在我们有了我们想要的间距。
如果布局需要在更大的断点上进行额外的调整,我们可以增加更多的列,并从那里开始。
.hero {
grid-template-columns: repeat(8, 1fr);
grid-template-areas:
"image image image image image image image image"
"..... text text text text text text .....";
}
开发工具的可视化。
还记得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 .....";
}
CodePen 嵌入回退
让我们来看看更复杂的东西
我们已经看了一个相当通用的例子和一个相对简单的例子。我们仍然可以通过更复杂的布局获得漂亮的ASCII布局的可视化效果。
让我们来看看这个:
我在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
元素的布局。让我们定义布局,并将idents分配给相应的元素,我们需要定位.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;
}
这就是我们的基本布局,所以让我们冒险进入一个更广泛的突破点。
@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 ...... .";
}
}
我敢打赌,你一定很清楚那会是什么样子,因为布局就在代码中!
如果我们决定进一步扩大规模,情况也一样:
.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 ..... ..... ..... ..... ..... .";
}
这里是完整的演示。
CodePen嵌入回退
我正在使用 "负margin
hack "来让第一张图片与标题重合。
收尾工作
我很好奇是否还有人使用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
,在列和行的方向上都是如此。