本文翻译自《An Interactive Guide to CSS Grid 》。
CSS Grid是CSS语言中最令人惊叹的部分之一。它为我们提供了大量的新工具,可以用来创建复杂又流畅的布局。
CSS Grid非常复杂。我花了很长时间才真正适应它!
在本教程中,我将分享在我使用CSS Grid的过程中,我经历的一些精彩的时刻。你将学习这种布局模式的基础知识,并了解如何用它做一些很酷的事情。
浏览器支持情况?
CSS Grid是构建CSS布局的最现代的工具,但它并不是“前沿”。自2017年以来,所有主流浏览器都支持它!
根据caniuse的数据,97.8%的用户支持CSS Grid 。Flexbox仅超过它约0.5%!
心智模型
CSS由几种不同的布局算法 组成,每种算法都是为不同类型的用户界面设计的。默认布局算法Flow layout是为数字文档设计的。表格布局是为表格数据而设计的。Flexbox布局用于沿单个轴分配子条目。
CSS Grid是最新、最伟大的布局算法。它非常强大:我们可以用它来构建复杂的布局,这些布局可以根据许多约束流畅地适应。
在我看来,CSS Grid最不寻常的部分是网格结构 ,行和列 ,完全在 CSS 中定义 :
(译者注:案例请看原文)
使用CSS Grid,单个DOM节点被细分为行和列。在本教程中,我们用虚线突出显示行/列,但实际上它们是不可见的。
这太奇怪了!在所有其他布局模式中,创建这样的隔间 的唯一方法是添加更多的DOM节点。例如,在Table布局中,每一行都使用<tr>创建,该行中的每个单元格都使用<td>或<th>创建:
<table>
<tbody>
<!-- First row -->
<tr>
<!-- Cells in the first row -->
<td></td>
<td></td>
<td></td>
</tr>
<!-- Second row -->
<tr>
<!-- Cells in the second row -->
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
与表格布局不同,CSS Grid允许我们完全从CSS内部管理布局。我们可以随心所欲地分割容器,创建我们的网格子节点可以用作锚点的隔间。
网格容器使用流式布局(Grid flow)
我们使用display属性选择网格布局模式:
.wrapper {
display: grid;
}
默认情况下,CSS Grid使用单列,并将根据子条目的数量根据需要创建一行或多行。这被称为隐式网格,因为我们还没有明确定义任何结构。
这就是它在默认情况下如何工作的:
(译者注:案例请看原文)
隐式网格是动态的;将根据子条目的数量添加或删除一行或多行。每个子条目都有自己的一行。
默认情况下,网格的容器元素的高度由其子条目的数量决定。它动态地增长和收缩。有趣的是,这甚至不是一个“CSS网格”的特性;网格的容器元素仍在使用文档流布局 (Flow layout) ,Flow布局中的块状元素(display:block)默认会动态地垂直增长以包含其内容。只有里面的子元素使用网格布局 (Grid layout) 来排列。
但是,如果我们给网格的容器元素一个固定的高度呢?在这种情况下,里面的行将平均分配它的高度 ,即每一行都有相同的高度:
(译者注:案例请看原文)
网格构建(Grid Construction)
默认情况下,CSS Grid将创建单列布局。我们可以使用grid-template-columns属性指定列:
(译者注:案例请看原文)
为清晰起见,添加了虚线
在上面的案例以及在整个教程中,我使用虚线来显示列和行之间的划分。在CSS网格布局中,这些线是不可见的,也无法使其可见。在本文章,这些虚线只做演示用。
通过grid-template-columns: 25% 75%;属性,我告诉CSS网格布局算法将元素分为两列。
列可以使用任何有效的CSS长度值 来定义,包括像素、rems、视口单位等。此外,我们还可以使用一个新的单位——fr单位:
(译者注:案例请看原文)
fr代表“分数”。在这个例子中,我们说第一列消耗1单位的空间,而第二列消耗3单位的空间。这意味着总共有4个单位的空间,这成为分母。第一列消耗了1/4的可用空间,而第二列消耗了3/4。
fr单位为CSS网格带来了类似于Flexbox样式的灵活性。百分比和长度值创建了硬约束,而fr列可以根据需要自由增长和收缩,以包含其内容。
尝试缩小此容器以查看fr单位和百分比单位之间的差异:
(译者注:案例请看原文)
在上述案例中,我们的第一列有一张可爱的幽灵图片,它的宽度被设置为55px。但是,如果列太小而无法容纳它呢?
基于百分比单位的列是刚性的,因此我们的图像会溢出,溢出到列外。
基于fr单位的列是灵活的,因此列不会缩小到其最小内容的尺寸以下,即使这意味着会打破比例。
更准确地说:fr单位分配额外的空间。首先,列宽将根据列的内容计算。如果容器还有剩余空间,将根据fr单位的值进行按比例分配。这与我在《Flexbox交互式指南》 中讨论的flex-grow属性的功能非常相似。
总的来说,这种灵活性是一件好事。百分比单位太严格了。
我们可以在gap中看到一个完美的例子。gap是一个神奇的CSS属性,它在网格中的所有列和行之间添加了固定数量的空间。
看看当我们在百分比单位和fr单位之间切换时会发生什么:
(译者注:案例请看原文)
请注意,当使用基于百分比单位的列时,内容是如何溢出到网格容器之外的。这是因为百分比单位是使用网格容器的总面积来计算的。这两列占用了网格容器内容区域的100%,并且不允许收缩。当我们添加16px的间隙时,这两列别无选择,只能溢出网格容器。
相比之下,fr单位是根据网格容器的额外空间计算的。在这种情况下,由于设置了间隙gap:16px;,网格容器的额外的空间减少了16px。CSS网格算法在两个网格列之间按比例分配剩余空间。
gap 与 grid-gap
当CSS Grid首次引入时,grid-gap属性用于在列和行之间添加空间。然而,很快,社区意识到,在Flexbox中拥有这个功能也会很棒。因此,该属性被赋予了一个更通用的名称:gap。
如今,grid-gap已被标记为弃用,浏览器将其取别名为gap。这两个属性做完全相同的事情。它们都有几乎相同的浏览器支持,约96% 。
因此,我建议使用gap而不是grid-gap,无论你是使用Flexbox还是CSS grid。但也不强制转换现有的grid-gap声明。
隐式的行和显式的行
如果我们在两列网格中添加两个以上的子元素,会发生什么?
好吧,让我们试试:
(译者注:案例请看原文)
有趣!我们的网格获得了第二行。网格算法希望确保每个子元素都有自己的网格单元。它将根据需要生成新行以实现这一目标。这在我们有可变数量的子元素(例如照片网格)并且我们希望网格能自动扩展的情况下很方便。
不过,在其他情况下,我们希望显式定义一行或多行,以创建特定的布局。我们可以使用grid-template-rows属性来实现这一点:
(译者注:案例请看原文)
通过定义grid-template-rows和grid-template-columns,我们创建了一个显式的网格。这非常适合构建页面布局,比如在本教程的顶部的“圣杯”布局。
重复助手
假设我们正在构建一个日历:
对于这种场景,CSS Grid是一个很好的工具。我们可以将其构造为7列网格,每列占用1个单位的空间:
.calendar {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
}
这是可行的,但要写出每一个1fr有点烦人。想象一下,如果我们有50列!
幸运的是,有一个更好的方法来解决这个问题:
.calendar {
display: grid;
grid-template-columns: repeat(7, 1fr);
}
repeat函数将为我们进行复制/粘贴。我们只需要说我们想要7列,每列宽1fr。
以下是这个日历的完整代码:
(译者注:案例请看原文)
分配子项
默认情况下,CSS网格布局算法会将每个子元素分配到第一个未被占用的网格单元,就像商人在浴室地板上铺设瓷砖一样。
不过,有一件很酷的事情:我们可以将我们的子元素分配给任何一个单元格!甚至可以跨越多行/多列。
这是一个交互式演示,展示了CSS网格布局算法是如何工作的。单击/按下或拖动以将子元素放置在网格中:
(译者注:案例请看原文)
grid-row和grid-column 属性允许我们指定网格布局的子元素应占用的网格空间。
如果我们想让子对象占据一行或一列,我们可以通过一个编号来指定它。grid-column:3将设置子元素坐在第三列。
网格子元素还可以拉伸并跨越多行或多列。此语法使用斜线来描述开始和结束:
.child {
grid-column: 1 / 4;
}
乍一看,这看起来像一个分数1/4。然而,在CSS中,斜线字符不用于分割,而是用于分隔一组值。在这种情况下,它允许我们在单个声明中设置开始列和结束列。
这实质上是对此的简写:
.child {
grid-column-start: 1;
grid-column-end: 4;
}
这里有一个狡猾的陷阱:我们提供的数字是基于列线,而不是列索引。
用图表最容易理解这个陷阱:
容易令人困惑的是,一个4列的网格实际上有5条列线。当我们为网格分配一个子元素时,我们使用这些线来锚定它们。如果我们想让我们的子元素跨越前3列,它需要从第1个列线开始,到第4个列线结束。
负数的列线
在像英语这样的从左到右读写的语言中,我们从左到右数列。然而,对于负的列号或行号,我们也可以从右向左反向计数。
.child {
/* 表示从右往左数的第2条列线 */
grid-column: -2;
}
真正酷的是,我们可以混合使用正数和负数。看看这个:
请注意,即使我们根本没有更改网格列的分配,子元素也跨越了网格的整个宽度!
我们在这里说,我们的子元素应该跨越第一列到最后一列。无论有多少列,这个方便的声明都会按预期工作。
你可以在我的博客文章“使用CSS网格布局进行全出血布局 ”中看到这个技巧的一个实际用例。
网格区域(grid areas)
好了,是时候谈谈CSS Grid最酷的部分之一了。😄
假设我们正在构建此布局:
根据我们迄今为止所学到的知识,我们可以这样构建它:
.grid {
display: grid;
grid-template-columns: 2fr 5fr;
grid-template-rows: 50px 1fr;
}
.sidebar {
grid-column: 1;
grid-row: 1 / 3;
}
.header {
grid-column: 2;
grid-row: 1;
}
.main {
grid-column: 2;
grid-row: 2;
}
这是可行的,但有一种更符合人体工程学的方法:使用网格区域(grid areas)相关属性。
下面是它的样子:
(译者注:案例请看原文)
与之前一样,我们使用grid-template-columns和grid-template-rows定义网格结构。但是,我们有一个奇怪的声明:
.parent {
grid-template-areas:
'sidebar header'
'sidebar main';
}
这是如何工作的:我们正在绘制我们想要创建的网格,就像我们在制作ASCII文字艺术一样。每一行文字代表网格的一行,每个单词都是我们为网格的特定切片命名的。看看它在视觉上是如何排序的。
然后,我们不是用grid-column和grid-row来分配子元素,而是用grid-area来分配它!
当我们希望特定区域跨越多行或多列时,我们可以在模板中重复该区域的名称。在这个例子中,“sidebar”区域跨越了两行,即第一列中的两个单元格成为了“sidebar”区域。
我们应该使用grid-area还是grid-column和grid-row?当构建显式布局时,我真的很喜欢使用grid-area。它允许我为网格的分配赋予语义意义,而不是使用难以理解的行/列编号。也就是说,当网格具有固定数量的行和列时,使用grid-area效果最佳。而grid-column和grid-row对于隐式网格很有用。
注意键盘用户
当涉及到网格分配时,有一个很大的问题:选项卡的顺序仍将基于 DOM 位置,而不是网格位置 。
用一个例子来解释会更容易。在这个例子里,我设置了一组按钮,并用CSS Grid排列它们:
(译者注:案例请看原文)
当涉及到网格分配时,有一个很大的问题:选项卡的顺序仍将基于 DOM 位置,而不是网格位置 。
用一个例子来解释会更容易。在这个例子里,我设置了一组按钮,并用CSS Grid排列它们:
(译者注:案例请看原文)
在“结果”窗格上,按钮似乎按顺序排列。从左到右,从上到下阅读,从一到六。
如果你使用的是带键盘的设备,试着按一下这些按钮。你可以通过单击左上角的第一个按钮(“One”),然后按Tab键一次移动一个按钮。
你应该看到这样的效果:
(译者注:案例请看原文)
从用户的角度来看,焦点轮廓在页面上毫无韵律或理由地跳跃。这是因为按钮的焦点是基于它们在 DOM 中的顺序排序的 。
为了解决这个问题,我们应该重新排序DOM中的网格子元素,使其与视觉顺序相匹配,这样我就可以实现从左到右、从上到下按视觉顺序进行制表。
CSS属性reading-order
CSS工作组意识到这是CSS网格布局的一个缺陷。必须将网格子元素的DOM顺序与视觉顺序相匹配,这违背了可以将子元素随意分配给特定网格单元格的目的!
修复工作正在进行中。将来,CSS属性reading-order应该允许我们指示浏览器忽略DOM顺序,并按照子元素在屏幕上的显示顺序聚焦子元素。
不幸的是,截至2024年9月,此属性尚未在任何浏览器中实现。你可以在Rachel Andrew的帖子“解决CSS布局和源代码顺序断开连接的问题 ”中了解有关此属性的更多信息。
对齐
在我们目前为止看到的所有示例中,我们的列和行都会拉伸以填充整个网格容器。然而,事实并非如此!
例如,假设我们定义了两列,每列宽90px。只要网格容器的宽度大于180px,就会有一些额外空间:
我们可以使用justify-content属性来控制列的分布:
(译者注:案例请看原文)