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日

相关推荐
y先森3 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy3 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189113 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿4 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡5 小时前
commitlint校验git提交信息
前端
虾球xz5 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇6 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒6 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员6 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐6 小时前
前端图像处理(一)
前端