CSS Grid 交互式指南(译)(下)

📃 原文地址:An Interactive Guide to CSS Grid

👨‍💼 原文作者: Joshw Comeau

📂 归类:CSS

🗓️ 初次发布:2023 年 11 月 21 日

🔄 最近更新:2025 年 5 月 9 日

我们接上篇 - CSS Grid 交互式指南(译)(上) 完成剩下的内容


👶 分配子元素(Assigning children)

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

不过这里有个很酷的事情:我们可以将子元素分配到任意的单元格!

子元素甚至可以跨越多个行或列

👇 下面这个交互式示例展示了这个特性。你可以点击/按住并拖动,以将某个子元素放置在网格中的不同位置:

💡 译注:此处示例为可视化拖动演示,展示了 grid 中 grid-column, grid-row 等属性的作用。强推大家点进原文自己互动看看~

grid-rowgrid-column 这两个属性可以让我们指定子元素应当占据哪些轨道(track)

如果我们希望子元素只占据一行或一列 ,可以直接通过数字指定。

例如:

css 复制代码
grid-column: 3;

这表示该子元素会放在第 3 列 中。CSS Grid 也允许子元素跨越多行或多列 。这时候的语法会使用 / 斜杠来标识起始和结束的位置,例如:

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

乍一看这像是一个分数 ¼,但在 CSS 中,斜杠 / 并不是用来做除法的 。它的作用是将多个值分隔开。在这里,它的作用是:

表示从第 1 条列线(column line)开始,到第 4 条列线结束。

也就是说:这个子元素会跨越第 1~3 列,占据了三列的宽度。这是下面这种写法的简写形式:

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

注意这里的数字是基于"网格线(grid lines)"的编号,而不是"网格列(grid columns)"的编号。

👇 这个区别配合示意图会更容易理解:

译注:每一列的两侧都有"线",线的编号从 1 开始。第一列在第 1-2 条线之间,第二列在第 2-3 条线之间,以此类推。所以跨越 3 列需要从 14

这点很容易让人迷惑:一个 4 列的 Grid 实际上会有 5 条列线(column lines)

这是因为每一列都有一个开始线和结束线,4 列自然就需要 5 条线来分隔它们。

当我们给 Grid 的子元素设置位置时,是基于这些"列线"来锚定位置的

✅ 举例:如果我们希望子元素横跨前 3 列,就需要它从第 1 条列线开始,到第 4 条列线结束: grid-column: 1 / 4

默认情况下,在像英语这样的 从左到右(LTR)语言环境中,列线是从左向右编号的(1、2、3...)。

但我们也可以使用负数,从右向左进行编号。

css 复制代码
.child {
  /* 表示从右边数第二条线开始(即倒数第 2 列) */
  grid-column: -2;
}

这在某些场景下非常有用,比如我们希望某个元素总是靠右对齐、但不确定总列数的时候。

🧠 正负线号混用

更妙的是,我们还可以混用正数和负数,用起来非常灵活:

css 复制代码
.child {
   /* 从第 2 列线开始,到倒数第 2 列线结束 */
   grid-column: 2 / -2;
}

这段代码表示:子元素横跨了中间的若干列(不包括最左和最右两列),不管总共有多少列都适配得很好。 我们可以注意到子元素虽然我们并没有修改 grid-column 的设置,它却能横跨整个网格的宽度 ! 这是因为我们将子元素设置为从 第一条列线最后一条列线

css 复制代码
grid-column: 1 / -1;

不管这个 Grid 有多少列,这个写法都能让元素横向铺满整个网格,非常方便。

📌 实战案例:这种技巧的一个典型用法可以参考我写的一篇博客
《使用 CSS Grid 实现 Full-Bleed(全宽)布局》


📐 Grid Areas(网格区域)

现在我们来聊聊 CSS Grid 最酷的一项能力之一:命名网格区域(Grid Areas) 。😄

假设我们正在构建如下的页面布局结构:

使用我们已经学到的知识,我们可以这样构建这个布局:

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(网格区域)

我们可以这样写:

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

这段代码做了什么呢?

我们使用 grid-template-areas 像画 ASCII 图那样,把网格的结构"画"出来了。每一行代表一行网格,每个单词表示一个区域的名字。看起来就像是一个文字版的网格结构,非常直观。

然后,对于每个子元素,我们不再用 grid-columngrid-row 去定位它,而是使用 grid-area 来匹配名字即可:

css 复制代码
.sidebar {
  grid-area: sidebar;
}
.header {
  grid-area: header;
}
.main {
  grid-area: main;
}

当我们希望某个区域跨越多行或多列时,只需在模板中重复该区域名即可。比如上面 sidebar 就是跨两行的,因为它在模板的第一列出现了两次。


那么我们该用区域(area)还是行列(row/column)?

如果你在构建一个 明确的布局结构 (像上面这种 header/sidebar/main),我推荐使用 grid-area ------ 这样我们赋予了网格更强的"语义性",可读性也更高,不像纯数字那样晦涩。

不过要注意一点:grid areas 更适合固定结构的网格 (固定行列数)。如果是动态或隐式网格,还是得用 grid-row / grid-column


💡 注意键盘用户的可访问性问题**

说到这里,有一个隐藏的"陷阱":Grid 的视觉顺序不会影响键盘导航的顺序。

什么意思呢?Tab 键的焦点移动顺序,仍然是基于 DOM 中的元素顺序,而不是你通过 CSS Grid 排列出来的视觉顺序。

举个例子:

我们放了一组按钮在页面上,用 CSS Grid 排列成 5 行 5 列,肉眼看上去它们顺序是:

复制代码
1 2 
3 4 
5 
6

但是如果它们在 DOM 结构中是这样排列的:

html 复制代码
<button>One</button>
<button>Four</button>
<button>Two</button>
<button>Five</button>
<button>Three</button>
<button>Six</button>

那么用户在使用键盘按 Tab 时,焦点移动顺序依然是 DOM 顺序,即:

复制代码
1 ➜ 4 ➜ 2 ➜ 5 ➜ 3 ➜ 6

译者注:这儿原文有一个视频 www.joshwcomeau.com/images/inte...

可以看到,聚焦轮廓(focus outline)会在页面中跳来跳去,用户很难理解焦点到底在哪。这是因为按钮的聚焦顺序仍然基于 DOM 中的出现顺序 ,而不是它们在页面上的视觉排列

为了修复这个问题,我们应该调整 DOM 中网格子元素的顺序,使其与视觉上的排列一致。这样,用户就能按照从左到右、从上到下的顺序正常使用 Tab 键导航。


🧭 reading-order CSS 属性

CSS 工作组意识到这是 CSS Grid 的一个缺陷 ------ 必须让 DOM 顺序和视觉顺序保持一致,这和"能自由指定子元素在哪个网格单元格"这一 Grid 的优势有点背道而驰。

一个修复方案已经在推进中:未来,浏览器可能支持一个名为 reading-order 的 CSS 属性,它能让你告诉浏览器按照视觉顺序而非 DOM 顺序来聚焦元素。

不过,截至 2024 年 9 月还没有任何浏览器实现 这个属性。你可以在 Rachel Andrew 的文章《解决 CSS 布局与源码顺序的脱节问题》 中了解更多细节。


📐 对齐方式(Alignment)

到目前为止,我们示例中的所有行和列都会自动拉伸以填满整个网格容器。但实际上,这并不是必须的!

比如,如果我们定义了两个宽度都是 90px 的列,而父容器宽度大于 180px,那么在网格的右边就会出现一些空白区域:

我们可以使用 justify-content 属性来控制列在水平方向上的分布方式

如果你熟悉 Flexbox 布局算法,那么你可能觉得这些对齐属性非常眼熟。实际上,CSS Grid 在 Flexbox 首次引入的对齐属性基础上,做了进一步的扩展

不过这里有一个关键的区别:

我们现在对齐的是列(或行)本身,而不是网格项(items)!

也就是说,justify-content 是用来控制整个网格的"间隔盒子"(compartments)如何分布的,而不是控制每个具体子项的位置。

如果我们希望控制网格项本身在每个格子里的位置,就需要使用justify-items属性:

(编者注:这儿是互动式demo,再次强烈大家点进原文试用~)

当我们将一个 DOM 节点放进一个 grid 容器中时,默认行为是:

该节点会横向拉伸,占满整个列的宽度

这种行为其实类似于传统的流式布局(Flow layout)中,<div> 元素默认会横向撑满它的父容器。但有了 justify-items,我们可以自定义这种行为:它让我们能跳脱对称列宽的限制,更加灵活地控制布局样式。

当我们把 justify-items 设置为非 stretch 时,子元素就会按其内容尺寸决定宽度,不会强行拉伸。结果是:同一列中的不同项可以有不同的宽度,看起来更自然也更灵活。

如果你只想某一个 grid 子元素的对齐方式不同 ,我们甚至可以使用justify-self控制单个子项对齐:

justify-items 不同,justify-self设置在子项上的 ,它用来控制单个 grid 子元素在单元格内的水平对齐方式。

我们可以这样理解:

justify-items 是在父元素上设置的,它为所有 grid 子元素设定了一个默认的 justify-self


🎯 对齐行(Aligning rows)

前面我们一直在讨论水平方向(横向) 的对齐方式,现在我们来看看 垂直方向(纵向) 的对齐。 CSS Grid 提供了另一套对齐属性,用于控制垂直方向的对齐:

align-content 就像 justify-content,但它作用于 ,而不是列。 同理,align-items 就像 justify-items,但它控制的是子元素在网格区域中的垂直对齐方式,而不是水平方向。 我们可以进一步总结如下:

属性前缀 对象 方向
justify- 控制 列(columns) 水平方向
align- 控制 行(rows) 垂直方向

再往下细分:

属性 作用范围 控制内容
*-content 整个网格结构 控制整体网格布局的对齐方式
*-items 所有 grid 子项 控制每个子项在单元格中的对齐方式
*-self 单个 grid 子项 控制某个子项在单元格中的对齐方式

🎯 双轴居中技巧(Two-line centering trick)

这是作者最喜欢的 CSS Grid 小技巧之一:

只用两行 CSS,就可以让某个元素在容器中水平和垂直居中

place-content 是一个简写属性,相当于同时设置:

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

我们之前学到:

  • justify-content 控制 列的对齐方式(横向)
  • align-content 控制 行的对齐方式(纵向)

那么 place-content: center 的效果就是:

✅ 将网格的行与列都居中对齐

在这个例子中,我们使用了一个隐式网格(implicit grid),里面只有一个子元素,所以其实就是一个 1 × 1 的网格。 使用 place-content: center,就是把这一个单元格定位在整个容器的中心位置。


💡 总结:

虽然现代 CSS 提供了很多居中元素的方式,比如的(这部分内容是译者补充):

  • flex + justify-content/align-items
  • grid + place-items
  • position: absolute + transform
  • margin: auto
  • text-align/line-height(早期用法)

但要做到只写两行 CSS 、而且不管容器尺寸、子项尺寸都能完美双轴居中的,目前最优雅、最简洁的方式可能就是:

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

冰山一角 💡

在这篇教程中,我们已经学习了 CSS Grid 布局算法中一些最基础、最核心的内容 。 但说实话,这些只是冰山一角,还有大量高级用法和技巧 我们还没涉及! 如果你觉得这篇文章对你有帮助,那你也许会对我整理的一份更系统、更深入的学习资源感兴趣。

它的名字叫:CSS for JavaScript Developers

译者注:剩下的内容是介绍这个课程的相关内容,感兴趣的同学可以自己去看哦,就不在这里搬运啦

相关推荐
拾光拾趣录5 小时前
Flexbox 布局:从“垂直居中都搞不定”到写出响应式万能布局
前端·css
小悟空6 小时前
[AI 生成] Flink 面试题
大数据·面试·flink
编程猪猪侠7 小时前
Tailwind CSS 自定义工具类与主题配置指南
前端·css
一只毛驴8 小时前
浏览器中的事件冒泡,事件捕获,事件委托
前端·面试
一只叫煤球的猫8 小时前
你真的处理好 null 了吗?——11种常见但容易被忽视的空值处理方式
java·后端·面试
KarrySmile9 小时前
Day04–链表–24. 两两交换链表中的节点,19. 删除链表的倒数第 N 个结点,面试题 02.07. 链表相交,142. 环形链表 II
算法·链表·面试·双指针法·虚拟头结点·环形链表
一只毛驴9 小时前
谈谈浏览器的DOM事件-从0级到2级
前端·面试
在未来等你10 小时前
RabbitMQ面试精讲 Day 5:Virtual Host与权限控制
中间件·面试·消息队列·rabbitmq
Hilaku11 小时前
深入background-image:你可能不知道的几个性能优化与高级技巧
前端·css