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
通过赋值 grid-template-columns
— 25%
和 75%
— 这两个值,我告诉 CSS 网格算法将元素分成两列。
可以使用任何有效的 CSS CSS <length-percentage> value值来定义列,包括像素、rems、视口(viewport)单位等。此外,我们还可以使用一个新单位 fr
单位:
Code Playground
Result
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
有意思!我们的网格增加了第二行。网格算法希望确保每个子网格都有自己的网格单元。为了实现这一目标,它会根据需要生成新行。如果我们的 items 数量不固定(例如照片网格),而我们又希望网格能够自动扩展,那么这就非常方便了。
但在其他情况下,我们需要明确定义行,以创建特定布局。我们可以使用 grid-template-rows
属性来做到这一点:
Code Playground
Result
通过定义 grid-template-rows
和 grid-template-columns
,我们创建了一个显式网格。这非常适合构建页面布局,比如本教程顶部的 “Holy Grail”? 布局。
假设我们正在制作一个日历:
CSS 网格是实现此类功能的绝佳工具。我们可以将其结构化为 7 列网格,每列占用 1 个单位的空间:
css
这样做是可行的,但要计算每一个 1fr
’s有点烦人。试想一下,如果我们有 50 个列!
幸运的是,有一种更好的方法可以解决这个问题:
css
这个 repeat
将为我们完成复制/粘贴。我们需要 7 列,每列宽 1fr
。
如果你好奇的话,这里有显示完整代码的示例:
Code Playground
Result
Link to this heading分配子元素
默认情况下,CSS 网格算法会将每个子元素分配到第一个未占用的网格单元,就像工人在浴室地板上铺瓷砖一样。
不过最酷的是: 我们可以将项目分配到任何我们想要的单元格中!子单元格甚至可以跨越多行/列。
下面是一个互动演示,展示了如何操作。 点击/按下并拖动 ,在网格中放置一个子 网格:
通过 grid-row
和 grid-column
属性,我们可以指定网格子元素应占用的行列。
如果我们想让子元素占据某一行或某一列,可以用数字来指定 grid-column: 3
将设置子元素位于第三列。
网格子元素也可以跨越多行/列。其语法使用斜线来划分开始和结束:
css
乍一看,这就像一个分数,即 ¼。但在 CSS 中,斜线字符不是用来分割的,而是用来分隔一组数值。在本例中,它允许我们在单个声明中设置起始列和终止列。
这本质上是一种速记:
css
这里有一个诡异的问题: 我们提供的数字是基于列行,而不是列索引。
通过图表最容易理解这个问题:
令人困惑的是,4 列网格实际上有 5 条列线。当我们为网格分配一个子网格时,我们会使用这些列线来锚定它们。如果我们想让子网格跨越前 3 列,它就需要从第 1 行开始,在第 4 行结束。
Link to this headingGrid areas
好了,是时候谈谈 CSS 网格最酷的部分之一了。 😄
假设我们正在构建这个布局:
根据我们目前所学到的知识,我们可以这样安排:
css
这种方法可行,但还有一种更符合人体工程学的方法: grid areas.
看起来是这样的:
和之前一样,我们用 grid-template-columns
和 grid-template-rows
来定义网格结构。但是,我们又有了这个奇怪的声明:
css
具体原理如下: 我们画出想要创建的网格,就像在制作 ASCII art? 艺术作品一样。每一行代表一行,每一个单词都是我们给网格中某一片的命名。看到它看起来和网格有几分相似了吗?
然后,我们不再给子元素分配 grid-column
和 grid-row
,而是给它分配 grid-area
!
当我们希望某个区域跨越多行或多列时,可以在模板中重复该区域的名称。在本例中, “sidebar” 区域横跨两行,因此我们在第一列的两个单元格中都写上 sidebar
我们应该使用区域(areas),还是行/列? 在构建这样的明确布局时,我非常喜欢使用区域。它能让我为网格分配赋予语义,而不是使用难以捉摸的行/列数字。不过,当网格有固定的行数和列数时,区域的效果会更好。grid-column
和 grid-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-content
与 justify-content
类似,但它影响的是行而不是列。同样, align-items
与 justify-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