CSS Grid交互指南

非原创,翻译自An Interactive Guide to CSS Grid

建议结合阅读原文,原文示例可交互

介绍

CSS Grid 是 CSS 语言里最令人赞叹的部分之一,它为我们提供了许多新型工具,可以用这些工具来创建精致且流畅的布局。

它还出人意料地复杂。我花了相当长的时间才真正适应 CSS 网格!

本教程中,我将分享我在学习 CSS 网格布局时获得的最大💡 启发时刻。你将学习这种布局模式的基础知识,并了解如何用它做一些很酷的事情。✨

浏览器支持?

CSS Grid 是构建 CSS 布局最现代的工具,但它并不是 "前沿" 技术;它自 2017 年以来就受到所有主流浏览器的支持!

根据 caniuse 的数据,CSS Grid 拥有 97.8% 的用户支持率。支持程度非常高;Flexbox 的支持率也仅高出 0.5% 左右!

心智模型

CSS 由多种不同的布局算法组成,每种算法都针对不同类型的用户界面而设计。默认布局算法流式布局(Flow layout)适用于数字文档。表格布局(Table layout)适用于表格数据。弹性盒布(Flexbox)局用于沿单个轴分布项目。

CSS 网格(Grid)是最新且最佳的布局算法。它非常强大:我们可以用它基于一些条件构建灵活调整的复杂布局。

在我看来,CSS Grid 最不寻常的部分是它仅使用 CSS 定义了网格结构(即行和列):

使用用 CSS Grid,可以将一个 DOM 节点细分为行与列。在本教程中,我们使用虚线高亮显示行/列,但实际上它们都是不可见的。

这真是太奇怪了!在其他所有布局模式中,创建这样的分隔区域唯一的方法就是增加更多的 DOM 节点。例如,在表格布局中,每一行都是用一个 <tr> 来创建的,而该行内的每个单元格则使用 <td><th> 来实现:

html 复制代码
<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 内部管理布局。我们可以按照自己的意愿切割容器,创建出网格子元素可以作为锚点使用的分隔区域。

Grid 布局

我们通过 display 属性选择进入网格布局模式:

css 复制代码
.wrapper {
  display: grid;
}

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

工作原理如下:

隐式表格是动态的,会根据子元素的数量添加或移除行。每个子元素都有它自己的行。

默认情况下,网格容器的高度由其子元素决定。它会动态地增长和缩小。有趣的是,这甚至不是"CSS 网格"的特性;网格容器仍然使用流式布局,而流式布局中的块级元素会垂直增长以包含它们的内容。只有子元素是使用网格布局来排列的。

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

Grid 排列

默认情况下,CSS 网格会创建一个单列布局。我们可以使用 grid-template-columns 属性来指定列:

grid-template-columns 传入两个值 --- 25%75% --- 我告诉 CSS 网格算法将元素切分成两列。

列可以用任何合法的 CSS 值来定义,包括像素、rem、视口单位等等。此外,我们还可以使用一个新的单位,也就是fr单位:

"fr"表示"fraction"。在这个示例中,我们说第一列应占据 1 个单位的空间,而第二列占据 3 个单位的空间。这意味着共有 4 个单位的空间,这将成为分母。第一列消耗了可用空间的 ¼,而第二列消耗了 ¾。

fr 单位为 CSS 网格带来了 Flexbox 风格的灵活性。百分比和 <length> 值创建固定约束,而 fr 列可以根据需要自由增长和缩小,以包含它们的内容。

请尝试增减该容器,以查看差别:

在这种场景下,我们的第一列有一个毛茸茸的小幽灵,显示的宽度明确为 55 像素。但如果列太小无法包含它该怎么办?

  • 基于百分比的列是固定的,因此我们的虚像会"溢出",溢出到列之外。

  • 基于fr的列是灵活的,即使这意味着破坏了比例,列也不会缩小到其最小内容大小以下。

更确切地说,fr 分配的是多余空间。首先,列宽将根据其内容进行计算。如果有任何剩余空间,它将根据 fr 值进行分配。这与 flex-grow 非常相似,如我在 Flexbox 交互式指南 中所述。

总体而言,这种灵活性是一件好事。百分比太死板了。

我们可以通过 gap 看到这个原理的一个非常好的例子。gap 是一个神奇的 CSS 属性,它可以在网格中的所有列和行之间添加固定数量的间距。

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

当使用百分比时,注意内容是如何溢出网格父元素的吗?这是因为百分比是使用网格的总面积计算的。两个列占用了父元素内容区域的 100%,并且不允许它们缩小。当我们添加 16px 的间距时,这些列别无选择,只能溢出容器。

相反,fr 单位是根据额外的空间来计算的。在这种情况下,额外的空间因 gap 而减少了 16 像素。CSS 网格算法会在两个网格列之间分配剩余的空间。

隐式和显式行

如果我们向一个两列的网格中添加超过两个子元素会发生什么?

好吧,我们来尝试一下:

有趣!我们的网格产生了新的行。grid算法想要确保每个子项都有它自己的网格单元格。它会根据需要生成新行,以实现这一目标。在项目数量变化的情况下(例如照片网格),且希望网格自动扩展时,这非常方便。

在其他情况下,我们想要显式的定义行,创建特定的布局。我们可以使用 grid-template-rows 属性来完成此操作:

通过定义 grid-template-rowsgrid-template-columns,我们创建了一个明确的网格。这是构建页面布局的完美选择,例如本教程顶部的"圣杯"布局。

repeat方法

想象一下我们要创建一个日历:

CSS 网格是用于这种类型事情的非常棒的工具。我们可以将其构建为 7 列网格,每列占用 1 个单位的空间:

这种方法可行,但不得不数每个 1fr 项有点烦人。想象一下,如果我们有 50 列!

幸运的是,有一个更优雅的解决方案:

css 复制代码
.calendar {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
}

repeat 函数将为我们完成复制/粘贴。我们说我们需要 7 列,每一列的宽度都是 1fr

子元素排列

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

不过最棒的是:我们可以把项目分配到我们想要的任何单元格里!孩子们甚至可以在多行/多列中展开。

这是一个展示此工作原理的交互式演示。点击/按住并拖动以将孩子放在网格中:

css 复制代码
.parent {
  display: grid;
  grid-template-columns:
    repeat(4, 1fr);
  grid-template-rows:
    repeat(4, 1fr);
}
.child {
  grid-row: 2;
  grid-column: 2;
}

grid-rowgrid-column 属性允许我们指定网格子元素应占据哪个轨道。

如果我们希望孩子占用单独一行或列,我们可以通过其编号来指定它。grid-column: 3 会将孩子设置在第三列中。

网格子组件也可以跨多行/列拉伸。其语法是使用斜杠来定义开始和结束:

css 复制代码
.child {
  grid-column: 1 / 4;
}

乍一看,这看起来像一个分数 ¼。然而,在 CSS 中,斜杠字符不用于除法,它用于分隔值组。在这种情况下,它允许我们在一个声明中设置开始和结束列。

这是一种简写,它相当于:

css 复制代码
.child {
  grid-column-start: 1;
  grid-column-end: 4;
}

这里有一个狡猾的陷阱:我们提供的值是基于列的"线"而不是列的"索引"。

从一张图中,最容易理解这个陷阱:

css 复制代码
.child {
  grid-row: 2 / 4;
  grid-column: 1 / 4;
}

虽然令人费解,但一个 4 列的网格实际上有 5 条列线。当我们将一个子元素分配到网格时,我们使用这些线来锚定它们。如果我们想要子元素跨越前 3 列,它需要从第 1 条线开始,并在第 4 条线上结束。

Grid areas

好的,现在要谈谈 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;
}

这种方法可行,但有一种更符合人体工学的方法可以用:网格区域

看看它长什么样:

css 复制代码
.parent {
  display: grid;
  grid-template-columns: 2fr 5fr;
  grid-template-rows: 50px 1fr;
  grid-template-areas:
    'sidebar header'
    'sidebar main';
}
.child {
  grid-area: sidebar;
}

一如既往,我们使用 grid-template-columnsgrid-template-rows 定义网格结构。然后,我们有了这个奇怪的声明:

css 复制代码
.parent {
  grid-template-areas:
    'sidebar header'
    'sidebar main';
}

这原理如下:我们正在画我们想要创建的表格,有点像我们制作 ASCII 艺术一样。每一行表示一行,每个单词都是我们给表格特定部分起的名字。明白它为何从视觉上看起来像表格了吗?

于是,我们不再为子元素分配 grid-columngrid-row,而是为其分配 grid-area

当我们希望某个区域跨越多行或多列时,可以在模板中重复此区域的名称。在此示例中,"sidebar"区域跨越两行,因此我们为第一列中的两个单元格都编写了"sidebar"。

应该使用区块还是行/列呢?在构建像这样明确的布局时,我非常喜欢使用区块。这可以让我为我的网格分配赋予语义含义,而不是使用难以理解的行/列编号。话虽如此,只有当网格有固定数量的行和列时,区块才能发挥最佳作用。grid-columngrid-row 可以用于隐式网格。

键盘问题

网格分配存在一个很大的问题:标签顺序仍将基于 DOM 位置,而非网格位置。

我们用一个例子来说明一下,在这个 playground 中,我准备了一组按钮,并用 CSS Grid 进行布局。

html 复制代码
<div class="wrapper">
  <button class="btn one">
    One
  </button>
  <button class="btn four">
    Four
  </button>
  <button class="btn six">
    Six
  </button>
  <button class="btn two">
    Two
  </button>
  <button class="btn five">
    Five
  </button>
  <button class="btn three">
    Three
  </button>
</div>

按钮似乎有顺序。从左向右、从上到下阅读,我们从一到六。

如果你有带键盘的设备,请尝试通过这些按钮来切换。 您可以先点击左上角的第一个按钮("一"),然后连续按 Tab 键,逐个切换按钮。

您应该看到类似这样的内容:

用户看来,焦点轮廓没有任何道理地跳跃到页面的各个部分。发生这种情况是因为按钮的焦点基于它们在 DOM 中的顺序。

为了修复这个问题,我们应该在 DOM 中重新排列网格的子元素,以便它们与可视顺序匹配,这样我就可以从左到右,从上至下进行制表键切换。

对齐

在迄今为止的所有示例中,我们的列和行都延伸来填充整个网格容器。但这种情况并非一定要发生!

例如,我们定义了两个 90px 宽的列。只要网格父级大于 180px,那么就会在末端产生一些空白:

通过 justify-content 属性,我们可以控制列分布:

如果您熟悉 Flexbox 布局算法,那么这听起来可能很熟悉。CSS Grid 基于 Flexbox 首次引入的对齐属性,并将这些属性发挥到了极致。

最大的区别是,我们要调整的是 ,而不是元素本身。实质上,justify-content 让我们可以整理网格的隔间,以我们希望的方式在网格中分布。

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

当我们将一个 DOM 节点放入网格父元素中时,默认行为是使其跨越该列的整个宽度,就像流布局中的 <div> 会水平拉伸以填充其容器一样。但是使用 justify-items 可以调整这种行为。

这很有用,因为它允许我们摆脱列的刚性对称。当我们把 justify-items 设置为 stretch 以外的其他值时,子项将缩小至由其内容确定的默认宽度。结果,同一列中的项目可以具有不同的宽度。

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

与用于设置网格父级并控制所有网格子元素对齐方式的 justify-items 不同,justify-self 是在子元素上设置的。我们可以将 justify-items 看作在所有网格子元素上设置 justify-self 的默认值的方式。

对齐行

迄今为止,我们一直在谈论如何在水平方向上对齐内容。CSS 网格布局提供了一组额外的属性,用以在垂直方向上对齐内容:

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

更进一步分解为:

  • justify------处理列。

  • align --- 处理行。

  • content --- 处理 网格结构

  • item ------处理网格结构中的 DOM节点。

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

水平垂直居中

还有一件事我必须给你演示一下。这是我喜欢的小技巧之一,用 CSS Grid 实现的。

使用仅有的两个 CSS 属性,我们可以让子元素在容器中居中,无论水平还是垂直方向:

css 复制代码
.parent {
  display: grid;
  place-content: center;
}

place-content 是一个简写形式属性。它等同于以下代码:

css 复制代码
.parent {
  justify-content: center;
  align-content: center;
}

正如我们已经了解的,justify-content 控制了列的位置。align-content 控制了行的位置。在这种情况下,我们有一个具有一个子项的隐式网格,因此我们最终得到一个 1×1 网格。place-content: center 将行和列都推到中心位置。

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

更多内容

本教程涵盖了 CSS 网格布局算法的一些基本内容,但老实说,我们还没有谈论过 很多东西

如果你觉得这篇博文有帮助,你可能会对这个我精心制作的学习资源感兴趣,它非常深入。它叫《 CSS for JavaScript Developers.》。

本课程使用了与我的博客相同的技术,因此其充满了交互式说明。但也包含一些小视频、练习题、灵感源自现实世界的项目,甚至还有一些小游戏。

如果您觉得此博客文章有帮助,您会喜欢这门课程。它遵循类似的方法,但适用于整个 CSS 语言,并通过动手练习来确保您确实在发展新的技能。

专为使用 React/Angular/Vue 等 JS 框架的人打造。80% 的课程将教授 CSS 基础,我们还会学习如何将这些基础知识集成到现代 JS 应用程序中,如何构建 CSS 等。

如果你在学习 CSS 的时候遇到困难,我希望你能试试它。尤其在你已经熟悉 HTML 和 JS 的情况下,掌握 CSS 是能够彻底改变游戏规则的。当你掌握了三者之后,你便可以更容易地进入状态,真正地享受 Web 应用程序的开发。

希望这个指南对您有用。❤️

最后更新

2023年11月22日

相关推荐
bloxed39 分钟前
前端文件下载多方式集合
前端·filedownload
余生H1 小时前
前端Python应用指南(三)Django vs Flask:哪种框架适合构建你的下一个Web应用?
前端·python·django
LUwantAC1 小时前
CSS(四)display和float
前端·css
cwtlw1 小时前
CSS学习记录20
前端·css·笔记·学习
界面开发小八哥1 小时前
「Java EE开发指南」如何用MyEclipse构建一个Web项目?(一)
java·前端·ide·java-ee·myeclipse
米奇妙妙wuu1 小时前
react使用sse流实现chat大模型问答,补充css样式
前端·css·react.js
傻小胖1 小时前
React 生命周期完整指南
前端·react.js
梦境之冢2 小时前
axios 常见的content-type、responseType有哪些?
前端·javascript·http
racerun2 小时前
vue VueResource & axios
前端·javascript·vue.js
m0_548514772 小时前
前端Pako.js 压缩解压库 与 Java 的 zlib 压缩与解压 的互通实现
java·前端·javascript