3

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

 1 month ago
source link: https://www.techug.com/post/interactive-guide-to-grid/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Introduction

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

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

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

浏览器支持?

CSS Grid 是用 CSS 构建布局的最现代工具,但它并不完全是 “尖端 ”工具;自 2017 年以来,所有主流浏览器都支持它!

根据 caniuse 的数据, 97.8% 的用户支持 CSS Grid。这是 非常强 的浏览器支持;Flexbox 的支持率仅高出 0.5%!.

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

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

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

Grid Parent
Grid Children
<Header>
<Nav>
<Main>
<Footer>
Show Perspective

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

这太奇怪了!在其他布局模式中,创建类似隔间的唯一方法是添加更多 DOM 节点。例如,在表格布局中,每一行都使用 <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 网格让我们可以完全在 CSS 中管理布局。我们可以随心所欲地分割容器,创建网格子元素可用作空间的隔间。

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



.wrapper {
display: grid;

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

具体写法如下:

Show Perspective
Number of Children:3

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

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

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

.parent {
display: grid;
height: 300px;
Show Perspective
Number of Children:3

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

Code Playground

<style>
.parent {
display: grid;
grid-template-columns: 25% 75%;
</style>
<div class="parent">
<div class="child">1</div>
<div class="child">2</div>
</div>

Result

Enable ‘tab’ key

为清晰起见,添加了虚线

在上面的示例和本教程中,我使用虚线来显示列和行之间的划分。在 CSS 网格中,这些线是不可见的,也无法使其可见。

我在这里使用了一些 ✨ 魔法 ✨ ,用伪元素pseudo-element来伪造。如果你想了解具体方法,可以跳转到 “CSS ”选项卡。

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

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

Code Playground

<style>
.parent {
display: grid;
grid-template-columns: 1fr 3fr;
</style>
<div class="parent">
<div class="child">1</div>
<div class="child">2</div>
</div>

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 网格算法会在两列网格之间分配剩余空间。

gap vs. grid-gap

CSS Grid刚推出时, grid-gap 属性用于在列和行之间添加空间。但很快,社区就意识到,如果在 Flexbox 中也能实现这一功能,那将会非常棒。因此,该属性被赋予了一个更通用的名称 gap.

如今, grid-gap 已被标记为废弃属性,浏览器将其别名为 gap. 。这两个属性的作用完全相同。它们在浏览器中的支持率几乎 相同, 都在 96% 左右

因此,无论是使用 Flexbox 还是 CSS Grid,我都建议使用 gap 而不是 grid-gap。不过,在转换现有的 grid-gap 声明时,也不必过于着急。

隐式和显式行

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

好吧,让我们试一试:

Code Playground

<style>
.parent {
display: grid;
grid-template-columns: 1fr 3fr;
</style>
<div class="parent">
<div class="child">1</div>
<div class="child">2</div>
<div class="child">3</div>
</div>

Result

Enable ‘tab’ key

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

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

Code Playground

<style>
.parent {
display: grid;
grid-template-columns: 1fr 3fr;
grid-template-rows: 5rem 1fr;
</style>
<div class="parent">
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
</div>

Result

Enable ‘tab’ key

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

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

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



.calendar {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr;

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

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



.calendar {
display: grid;
grid-template-columns: repeat(7, 1fr);

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

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

Code Playground

<style>
.calendar {
display: grid;
grid-template-columns:
repeat(7, 1fr);
gap: 4px;
</style>
<!--
Normally, I'd use something like
React to generate these cells
dynamically, but I'm using plain
HTML here to keep things simple.
-->
<ol class="calendar">
<li class="day">1</li>
<li class="day">2</li>
<li class="day">3</li>
<li class="day">4</li>
<li class="day">5</li>
<li class="day">6</li>
<li class="day">7</li>
<li class="day">8</li>
<li class="day">9</li>
<li class="day">10</li>
<li class="day">11</li>
<li class="day">12</li>
<li class="day">13</li>
<li class="day">14</li>
<li class="day">15</li>
<li class="day">16</li>
<li class="day">17</li>
<li class="day">18</li>
<li class="day">19</li>
<li class="day">20</li>
<li class="day">21</li>
<li class="day">22</li>
<li class="day">23</li>
<li class="day">24</li>
<li class="day">25</li>
<li class="day">26</li>
<li class="day">27</li>
<li class="day">28</li>
<li class="day">29</li>
<li class="day">30</li>
<li class="day">31</li>
</ol>

Result

日历和可访问性

本日历是一个简单实用的示例,说明我们如何使用 CSS 网格创建特定布局。它并不打算用于正式应用中。

如果您想创建一个日期选取器,绝对应该使用这样的代码库 React Aria

分配子元素

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

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

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

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

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

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



.child {
grid-column: 1 / 4;

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

这本质上是一种速记:



.child {
grid-column-start: 1;
grid-column-end: 4;

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

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

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

负线号

在像英语这样从左到右的语言中,我们从左到右数列。但是,对于负数,我们也可以从右向左反向计数。

.child {
/* Sit in the 2nd column from the right: */
grid-column: -2;

最酷的是,我们可以混合使用正数和负数。看看这个

请注意,尽管我们完全没有改变 grid-column 的分配,但子网格还是横跨了整个网格的宽度!

我们在这里表示,我们的子元素应从第一列行跨度到最后一列行。无论有多少列,这个简便的声明都能如期工作。

您可以在我的博文中看到这种技巧的实际应用案例, “Full-Bleed Layout Using CSS Grid”.

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

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

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



.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-columnsgrid-template-rows 来定义网格结构。但是,我们又有了这个奇怪的声明:



.parent {
grid-template-areas:
'sidebar header'
'sidebar main';

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

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

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

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

注意键盘用户

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

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

Code Playground

<div class="wrapper">
<button class="btn one">
</button>
<button class="btn four">
</button>
<button class="btn six">
</button>
<button class="btn two">
</button>
<button class="btn five">
</button>
<button class="btn three">
Three
</button>
</div>

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. 属性。该属性可控制单个网格项在其单元格中的垂直位置。

双线居中技巧

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

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

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



.parent {
justify-content: center;
align-content: center;

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

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

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

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

Last Updated

November 22nd, 2023


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK