Skip to content

交互式教程教你精通掌握 CSS Grid 布局

Introduction

CSS Grid是 CSS 语言中最神奇的部分之一。它为我们提供了大量新工具,让我们可以利用这些工具创建精致流畅的布局。

它的复杂程度也 令人吃惊 我花了很长时间才真正适应 CSS Grid!

在本教程中,我将与大家分享我在使用 CSS Grid 的过程中所经历过的 💡 最激动人心的时刻。你将学习到这种布局模式的基本原理,并了解如何使用它实现一些很酷的功能。 ✨

CSS 由几种不同的布局算法组成,每种算法都针对不同类型的用户界面而设计。默认的布局算法--流式布局,是专为数字文档设计的。表格布局专为表格数据而设计。Flexbox 专为沿单个轴分布 items 而设计。

CSS Grid 是最新、最伟大的布局算法。它的功能强大得令人难以置信:我们可以用它来构建复杂的布局,并根据一系列约束条件进行流畅调整。

在我看来,CSS 网格最与众不同的地方在于,它的网格结构、行和列完全由 纯 CSS 定义:

通过 CSS Grid,单个 DOM 节点被细分为行和列。在本教程中,我们用虚线突出显示行/列,但实际上它们是不可见的。

这太奇怪了!在其他布局模式中,创建类似隔间的唯一方法是添加更多 DOM 节点。例如,在表格布局中,每一行都使用 <tr>创建,该行中的每个单元格都使用 <td><th>

html

与表格布局不同,CSS 网格让我们可以完全在 CSS 中管理布局。我们可以随心所欲地分割容器,创建网格子元素可用作空间的隔间。

我们通过 display 属性设计使用网格布局模式:

css

默认情况下,CSS 网格使用单列,并根据子元素的数量按需创建行。这被称为隐式网格,因为我们没有明确定义任何结构。

具体写法如下:

隐式网格是动态的;将根据子网格的数量添加和删除行。每个子网格都有自己的行。

默认情况下,网格父节点的高度由其子节点决定。它是动态增长和收缩的。有趣的是,这甚至不是 “CSS Grid”布局特性;网格父节点仍然使用 Flow 布局,Flow 布局中的块元素会垂直生长,以包含其内容。只有子元素才使用网格布局。

但是,如果我们给网格一个固定的高度呢?在这种情况下,总面积会被分成大小相等的行:

默认情况下,CSS Grid 将创建单列布局。我们可以使用grid-template-columns 属性指定列:

Code Playground

Result

Enable ‘tab’ key

通过赋值 grid-template-columns25%75% — 这两个值,我告诉 CSS 网格算法将元素分成两列。

可以使用任何有效的 CSS CSS <length-percentage> value值来定义列,包括像素、rems、视口(viewport)单位等。此外,我们还可以使用一个新单位 fr 单位:

Code Playground

Result

Enable ‘tab’ key

fr 代表 “分数(fraction)”在这个例子中,我们说第一列应该占用 1 个单位的空间,而第二列占用 3 个单位的空间。也就是说,总共有 4 个单位的空间,这就是分母。第一列占用了可用空间的 1/4,而第二列占用了 3/4。

这个 fr 单位为 CSS 网格带来了 Flexbox 风格的灵活性。百分比和 <length>值创建了硬约束,而 fr 列可以根据需要自由增减,以容纳其内容。

试着缩小这个容器,看看有什么不同:

在这种情况下,我们的第一列有一个可爱的幽灵,它的宽度明确为 55px。但如果该列太小,无法容纳它,该怎么办呢?

  • 基于百分比的列是刚性的,因此我们的幽灵图像会 溢出,从列中撑破。
  • 基于fr 的列是灵活的,因此列不会缩小到最小内容尺寸以下,即使这意味着要打破比例。

更准确地说: fr 单位分配 额外 空间。首先,将根据内容计算列宽。如果有剩余空间,将根据 fr 值进行分配。这与 Interactive Guide to Flexbox中讨论的 flex-grow非常相似。

一般来说,这种灵活性是件好事。百分比过于严格。

我们可以通过 gap 属性看到一个关于此的完美例子。 gap 是一个神奇的 CSS 属性,可以在网格中的所有列和行之间添加固定的间距。

看看我们在百分比和分数之间切换时会发生什么:

注意到使用基于百分比的列时,内容是如何溢出网格父级的?出现这种情况是因为百分比是使用网格总面积计算的。这两列占用了父网格 100%的内容区域,而且不允许缩小。当我们添加 16px 的 gap时,这两列内容就只能溢出容器之外了。

相比之下, fr 单位是根据额外空间计算的。在本例中,额外空间减少了 16px 作为 gap。CSS 网格算法会在两列网格之间分配剩余空间。

Link to this heading
隐式和显式行

如果在双列网格中添加两个以上的子网格,会发生什么情况?

好吧,让我们试一试:

Code Playground

Result

Enable ‘tab’ key

有意思!我们的网格增加了第二行。网格算法希望确保每个子网格都有自己的网格单元。为了实现这一目标,它会根据需要生成新行。如果我们的 items 数量不固定(例如照片网格),而我们又希望网格能够自动扩展,那么这就非常方便了。

但在其他情况下,我们需要明确定义行,以创建特定布局。我们可以使用 grid-template-rows 属性来做到这一点:

Code Playground

Result

Enable ‘tab’ key

通过定义 grid-template-rowsgrid-template-columns,我们创建了一个显式网格。这非常适合构建页面布局,比如本教程顶部的 布局。

假设我们正在制作一个日历:

CSS 网格是实现此类功能的绝佳工具。我们可以将其结构化为 7 列网格,每列占用 1 个单位的空间:

css

这样做是可行的,但要计算每一个 1fr’s有点烦人。试想一下,如果我们有 50 个列!

幸运的是,有一种更好的方法可以解决这个问题:

css

这个 repeat 将为我们完成复制/粘贴。我们需要 7 列,每列宽 1fr

如果你好奇的话,这里有显示完整代码的示例:

Code Playground

Result

Link to this heading
分配子元素

默认情况下,CSS 网格算法会将每个子元素分配到第一个未占用的网格单元,就像工人在浴室地板上铺瓷砖一样。

不过最酷的是: 我们可以将项目分配到任何我们想要的单元格中!子单元格甚至可以跨越多行/列。

下面是一个互动演示,展示了如何操作。 点击/按下并拖动 ,在网格中放置一个子 网格:

通过 grid-rowgrid-column 属性,我们可以指定网格子元素应占用的行列。

如果我们想让子元素占据某一行或某一列,可以用数字来指定 grid-column: 3 将设置子元素位于第三列。

网格子元素也可以跨越多行/列。其语法使用斜线来划分开始和结束:

css

乍一看,这就像一个分数,即 ¼。但在 CSS 中,斜线字符不是用来分割的,而是用来分隔一组数值。在本例中,它允许我们在单个声明中设置起始列和终止列。

这本质上是一种速记:

css

这里有一个诡异的问题: 我们提供的数字是基于列,而不是列索引。

通过图表最容易理解这个问题:

令人困惑的是,4 列网格实际上有 5 条列线。当我们为网格分配一个子网格时,我们会使用这些列线来锚定它们。如果我们想让子网格跨越前 3 列,它就需要从第 1 行开始,在第 4 行结束。

好了,是时候谈谈 CSS 网格最酷的部分之一了。 😄

假设我们正在构建这个布局:

根据我们目前所学到的知识,我们可以这样安排:

css

这种方法可行,但还有一种更符合人体工程学的方法: grid areas.

看起来是这样的:

和之前一样,我们用 grid-template-columnsgrid-template-rows 来定义网格结构。但是,我们又有了这个奇怪的声明:

css

具体原理如下: 我们画出想要创建的网格,就像在制作 艺术作品一样。每一行代表一行,每一个单词都是我们给网格中某一片的命名。看到它看起来和网格有几分相似了吗?

然后,我们不再给子元素分配 grid-columngrid-row,而是给它分配 grid-area!

当我们希望某个区域跨越多行或多列时,可以在模板中重复该区域的名称。在本例中, “sidebar” 区域横跨两行,因此我们在第一列的两个单元格中都写上 sidebar

我们应该使用区域(areas),还是行/列? 在构建这样的明确布局时,我非常喜欢使用区域。它能让我为网格分配赋予语义,而不是使用难以捉摸的行/列数字。不过,当网格有固定的行数和列数时,区域的效果会更好。grid-columngrid-row 对于隐式网格也很有用。

Link to this heading
注意键盘用户

在网格分配方面有一个大问题: 选项卡顺序仍将基于 DOM position, 位置,而不是网格位置。

举个例子会更容易解释。在这个游戏中,我设置了一组按钮,并使用 CSS 网格对它们进行了排列:

Code Playground

Result

在 “RESULT” 窗格中,按钮似乎是按顺序排列的。从左到右,从上到下,我们从 1 到 6。

如果您使用的是带键盘的设备,请尝试通过tab键焦距这些按钮。 您可以点击左上角的第一个按钮 (“One”), 然后按 Tab 键逐个移动按钮。

你应该看到这样的内容:

从用户的角度来看,焦点轮廓会在页面上无缘无故地跳动。出现这种情况的原因是,按钮是根据它们在 DOM 中出现的顺序进行聚焦的。

要解决这个问题,我们应该在 DOM 中重新排列网格子元素的顺序,使它们与视觉顺序相匹配,这样我就可以从左到右、从上到下地切换。

在我们迄今为止看到的所有示例中,我们的列和行都会拉伸以填满整个网格容器。但实际情况并非如此!

例如,假设我们定义了两列,每列宽 90px。只要网格父级大于 180px,末尾就会有一些死角:

我们可以使用 justify-content 属性控制列的分布:

如果你熟悉 Flexbox 布局算法,你可能会对它有种似曾相识的感觉。CSS Grid 是在 Flexbox 首次引入的对齐属性基础上进一步发展而来的。

最大的区别在于,我们对齐的是 columns列,而不是 items 本身。 从本质上讲, justify-content 可以让我们安排网格中的隔间,按照自己的意愿将它们分布在网格中。

如果我们想让项目本身在 列内 对齐,可以使用 justify-items 属性:

当我们将一个 DOM 节点放入一个网格父节点时,默认的行为是将其拉伸至整个列,就像 Flow 布局中的 <div> 会水平拉伸以填充其容器一样。不过,通过 justify-items,我们可以调整这种行为。

这很有用,因为它允许我们摆脱列的刚性对称。当我们将 justify-items 设置为 stretch以外的其他选项时,子项将根据其内容缩减到默认宽度。因此,同一列中的项目可以有不同的宽度。

我们甚至可以使用 justify-self 属性控制 特定 网格子网格的对齐方式:

不像 justify-items是在父网格上设置的,用于控制所有 所有 子网格的对齐方式,而 justify-self则不同,它是在子网格上设置的。我们可以将 justify-items 视为在所有子网格上设置 justify-self 默认值的一种方式。

到目前为止,我们一直在讨论如何在水平方向上对齐。CSS 网格提供了一组额外的属性,用于在 垂直方向 上对齐内容:

align-contentjustify-content 类似,但它影响的是行而不是列。同样, align-itemsjustify-items类似,但它处理的是网格区域内项目的 垂直对齐 ,而不是水平对齐。

再细分一下:

  • justify — 处理 columns.
  • align — 处理 rows.
  • content — 处理 grid 结构.
  • items — 处理 grid 结构中的 DOM 节点

最后,除了 justify-self属性,我们还有align-self. 属性。该属性可控制单个网格项在其单元格中的垂直位置。

Link to this heading
双线居中技巧

最后,我还想向你展示一件事。这是我最喜欢的 CSS 网格小技巧之一。

只需使用两个 CSS 属性,我们就能使子元素在容器中水平和垂直居中:

这个 place-content 属性是一种速记方法。它是一种语法糖:

css

正如我们所学的, justify-content 控制列的位置,而 align-content 则控制行的位置。在这种情况下,我们的隐式网格只有一个子网格,因此最终形成了一个 1×1 的网格。 place-content: center 会将行和列都推到中心位置。

在现代 CSS 中,将 div 居中的方法有很多,但这是我所知道的唯一一种只需要两个 CSS 声明的方法!

在本教程中,我们介绍了 CSS 网格布局算法的一些最基本的部分,但老实说,我们还有 很多东西没有讲到!

希望本教程对您有所帮助。 ❤️

Last Updated

November 22nd, 2023

Hits

3D portrait of the blog's author, Josh Comeau