最近,我们用于垂直对齐的工具有了很大改进。我早期作为网站设计师的经历涉及布局 960px 宽的主页设计,并使用 12 列网格在页面上水平对齐元素。媒体查询的出现需要进行一次重大的思维转变。当然,它解决了一些重大问题,但也引入了一些新问题,例如在元素换行或在布局中以其他方式移动时处理对齐问题。
让我们来看一个特定的场景:“条形”中包含一些按钮。这些按钮有两组,每组都包含在一个带有<legend>
的<fieldset>
中。
在大屏幕上,一切都准备就绪

这是一个非常基本的 CSS 方法,它实现了该布局,并且在移动断点处也分解成两“行”
.accessibility-tools fieldset {
width: 48%;
float: left;
margin-right: 1%;
}
/* Mobile */
@media only screen and (max-width: 480px) {
.accessibility-tools fieldset {
width: 100%;
}
}
在小屏幕上,我们得到以下结果

这就是问题所在:缺乏垂直对齐。假设我们希望将这些按钮对齐成更令人愉悦的排列方式,使按钮边缘彼此完美对齐。
首先,我们可以使用基于固定宽度像素的 CSS 解决方案,以强制元素在不同的断点处整齐排列,使用像这样的魔法数字
/* Mobile */
@media only screen and (max-width: 480px) {
legend {
width: 160px;
}
button {
width: 130px;
}
}

这解决了问题。
但是……这并不是一个完全灵活的解决方案。除了魔法数字(基于特定内容的固定像素值)之外,它还依赖于媒体查询的使用,而我希望能尽可能避免使用媒体查询。我在博客上的一篇名为“逐步放弃 Sass”的文章中讨论了这一点。
当我转向 CSS 的一些更现代的功能时,无需使用独特的代码来定位特定的屏幕尺寸了。
我需要每个按钮和标签都响应
- 可用空间
- 它们的内容
以及!
- 周围的其他元素。
可用空间
使用媒体查询的问题在于,它们没有考虑到正在重新对齐的元素周围的空间——Heydon Pickering 在其文章“Flexbox 神圣的信天翁”中的这张图片完美地证明了这一点

我真正想要的是,第二个<fieldset>
仅在它们无法整齐地排列在一行时才换行到第一个下方。
我们可以用 flexbox 实现吗?
flexbox 的一个主要卖点是它能够创建响应周围空间的元素。组件可以“伸缩”以填充额外的空间,并缩小以适应较小的空间。
对于这种情况,flex-wrap
属性设置为 wrap
。这意味着,一旦两个<fieldset>
元素不再适合在一行上,它们就会换行到第二行。
.wrapper--accessibility-tools {
display: flex;
flex-wrap: wrap;
}
flex-wrap
属性 有三个可用值。默认值为 nowrap
,将项目保留在一行上。wrap
值允许元素流到多行。然后是 wrap-reverse
,它允许项目换行,但——请注意——以反向方式(看起来很奇怪:当元素换行时,在从左到右的情况下,它们会出现在上一行的上方)。
使用 flexbox 使布局不再那么僵硬,但仍然需要 min-width
值来消除垂直对齐问题。所以:接近但没有成功。
网格可以帮助我们吗?
CSS 网格 是第一个专门为解决网页设计师和开发人员面临的持续布局问题而创建的 CSS 模块。它不是 flexbox 的直接替代品;相反,这两个模块通常协同工作得很好。
与 flexbox 一样,网格可用于允许每个<fieldset>
占用尽可能多或尽可能少的空间。直接进入主题,我们可以利用auto-fill
和 auto-fit
关键字(在 repeat()
函数中)允许网格项流到多行,而无需使用媒体查询。区别有点微妙,但在 Sara Soueidan 的文章“CSS 网格中的自动调整列大小:auto-fill 与 auto-fit”中得到了很好的解释。让我们使用auto-fit
.wrapper--accessibility-tools {
display: grid;
grid-template-columns: repeat(auto-fit, 450px);
grid-gap: 10px;
}
与 flexbox 示例一样,我仍然需要为标签的宽度设置一个绝对值,以便在<fieldset>
元素堆叠时对齐它们。
另一种使用网格的方法
CSS 网格还允许元素使用灵活的网格轨道根据其内容进行响应。除了百分比、相对单位或像素等其他长度值外,CSS 网格还接受分数单位 (fr
),其中1fr
将占用可用空间的一部分,2fr
将占用可用空间的两部分,依此类推。让我们在这里设置两列相等列
.wrapper--accessibility-tools {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 10px;
}
还有一个minmax()
函数,它创建可以伸缩以适应可用空间的网格轨道,但也不会缩小到指定大小以下。
.wrapper--accessibility-tools {
display: grid;
grid-template-columns: minmax(auto, max-content) minmax(auto, max-content);
grid-gap: 10px;
}
这两个演示都有效,并且没有任何绝对值或特定于设备的 CSS。但是,结果远非理想,每个网格现在都在不同的点响应。也许这不是一个大问题,但肯定不是很好。

发生这种情况是因为,当向容器添加display: grid
时,只有该容器的直接子元素会成为网格项。这意味着我们使用的内在大小单位仅与同一网格中的元素相关。
使用 subgrid
为了真正实现我的目标,我需要按钮和标签对兄弟网格容器中的元素做出反应。CSS 网格级别 2 包括 subgrid 功能。尽管我们一直能够嵌套网格,但每个网格容器中的元素都是独立的。使用 subgrid,我们可以设置使用父网格轨道的嵌套(子)网格。
这使得以前比较困难的一些数字模式变得更容易,特别是“卡片”模式,它似乎是最流行的展示 subgrid 好处的示例。如果没有 subgrid,每个卡片都被定义为一个独立的网格,这意味着第一个卡片中的轨道大小不会响应第二个卡片的高度变化。根据 Rachel Andrew 使用的示例,这里有一组简单的卡片

Subgrid 允许卡片使用父网格中定义的行,这意味着它们可以对周围卡片中的内容做出反应。

在此示例中,每个卡片仍然跨越三行轨道,但这些轨道现在是在父网格上定义的,允许每个卡片占用相同数量的垂直空间。
对于我们一直在处理的示例,我们不需要使用行。相反,我们需要根据兄弟网格中的内容调整列的大小。首先,让我们将父网格设置为包含两个<fieldset>
元素。这与我们之前在auto-fit
演示中看到的代码类似。
.wrapper--accessibility-tools {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
grid-gap: 10px;
}
然后我们将每个子网格定位到父网格上。
.sub-grid {
display: grid;
grid-column: span 3;
grid-template-columns: subgrid;
align-items: center;
}

现在,所有标签和按钮都与父网格的轨道对齐,保持一致。它们将根据可用空间具有相等的宽度。如果一行上没有足够的空间容纳每个嵌套网格,则第二个网格将换行到新的一行。

这一次,两个嵌套的网格项完美对齐。如果我们在其中一个按钮上引入一个较长的标题,网格也具有灵活性,其他元素将相应地做出反应。


浏览器兼容性
撰写本文时,对 subgrid 的支持并不理想。它仅在 Firefox 71+ 中受支持,尽管其他浏览器也发出了积极的信号。可以使用 CSS 功能查询为 Chrome 和 Edge 提供替代样式。
此浏览器支持数据来自 Caniuse,其中包含更多详细信息。数字表示浏览器从该版本及以上版本开始支持该功能。
桌面
Chrome | Firefox | IE | Edge | Safari |
---|---|---|---|---|
117 | 71 | 不支持 | 117 | 16.0 |
移动/平板电脑
Android Chrome | Android Firefox | Android | iOS Safari |
---|---|---|---|
127 | 127 | 127 | 16.0 |
请注意,在这些演示中,我在 fieldset 周围使用了额外的包装器。这是为了解决一个 表单元素与网格和弹性盒相关的错误。
<fieldset class="accessibility-tools__colour-theme">
<div class="wrapper"></div>
</fieldset>
布局 CSS 应用于包装器,并将 fieldset 设置为 display: contents
。
.accessibility-tools fieldset {
display: contents;
border: 0;
}
我真的很高兴 Firefox 现在支持 Subgrid 了!
我注意到您的 Subgrid 示例在宽度小于 500px 时无法正常工作。有没有办法在不使用媒体查询的情况下解决这个问题?
或者说,媒体查询是解决此问题的最佳方法吗?
很棒的文章。将把它添加到一些即将到来的项目中作为渐进增强。但一旦它获得更广泛的支持,那就太好了。