帮助我们发明 CSS Grid Level 3,又名“瀑布流”布局

更新 2024 年 10 月:自本文发布以来,CSS 工作组成员已得出结论,这里描述的所有网格能力——可变宽度轨道、显式放置、跨度和子网格——都值得包含在瀑布流布局中,并且可以高性能地实现。现在有一个官方的 W3C 工作草案,CSS Grid Layout Module Level 3,记录了其工作原理。然而,关于语法仍然存在争议,我们在一篇新文章中对此进行了阐述:帮助我们选择 CSS 瀑布流的语法

如果您已经制作网站多年,您就会知道使用 CSS 浮动布局网页是多么令人沮丧。管理大小和位置既繁琐又耗时。创造性常常是不可能的。CSS Grid2017 年通过 Grid Level 1 极大地缓解了这种痛苦,而 现在又有了 Grid Level 2,即 Subgrid。但即使是当今强大的 CSS,也并非所有设计师设想的布局都能实现。事实上,当 CSS Grid 发布时,最常被问到的问题之一是:“如何编写 CSS 来实现瀑布流布局?” 遗憾的是,在过去的七年里,答案是——你做不到。

什么是瀑布流布局?

我们所说的“瀑布流布局”是什么意思?它基本上是以下图像中所示的模式——内容像砖墙或石墙一样紧密排列。这就是它得名“masonry”(砌体)的原因。它也常被称为“waterfall layout”(流式布局),作为内容像瀑布一样向下流动的比喻。

Two dozen photos of different aspect ratios laid out using a "masonry" pattern

这种布局之所以受欢迎,是因为它解决了其他布局无法解决的一些问题。

  1. 它允许不同宽高比的内容,避免了为了将所有内容变成统一的矩形而进行裁剪或截断的需要。
  2. 它将内容分布在页面上(而不是逐列向下流动)。这遵循了您滚动页面时的自然阅读顺序。并且它允许网站在底部延迟加载额外内容而无需移动现有内容。

这种布局创建了大小均匀的列,没有任何行。很有可能因为这种布局需要 JavaScript,所以任何更具创意或更复杂的布局都很难实现——我们一直认为瀑布流布局应该只是一种具有大小均匀列的简单模式。让我们看看如果将其内置到 CSS 中会发生什么。

发明瀑布流

CSS 中用于“瀑布流布局”的机制最初由 Mozilla 于 2020 年 1 月提出,作为 CSS Grid 的扩展,并作为实验在 Firefox Nightly 中通过标志 实现。2022 年,Apple 开始在 Safari Technology Preview 中实现此 CSS Grid Level 3 提案(目前默认开启),我们一直在帮助推动网络标准走向成熟。

然而,关于 CSS 应如何处理瀑布流式布局,仍存在一些重大问题。有些人仍然怀疑这种功能是否应该成为 CSS Grid 的一部分,并希望它能成为一个独立的 display 类型。另一些人则质疑这种布局是否完全需要在网络上——他们不确定知名网站是否会使用它。鉴于存在如此根本性的分歧,任何浏览器都无法发布。我们必须首先在 CSS 工作组中达成共识。

这就是我们需要您帮助的地方。我们希望实际的网页设计师和开发人员参与讨论,并表达您想要什么。您的意见确实可以发挥作用。

在本文中,我们将介绍 CSS Grid Level 3 提案的工作原理以及如何使用其新功能。我们将向您展示我们为何相信这些功能应该成为 CSS Grid 的一部分,并解释如果 CSS 工作组改为创建 display: masonry 会有什么替代方案。然后,我们将邀请您加入辩论,帮助我们前进。请务必读到最后。

四个演示

为了说明我们 Apple 之所以相信此功能应该成为 CSS Grid 的一部分,我们创建了四个演示。如果您愿意,可以在 webkit.org/demos/grid3 亲自尝试。在支持 Grid Level 3 的浏览器中查看这些演示——目前是 Safari Technology Preview 或在开启 功能标志后的 Firefox。

请注意,每个演示都有一个控制面板,其中相关布局代码打印在页面上。开启“编号项目”可以查看内容 HTML 顺序与该内容布局位置之间的关系。

Screenshot showing the controls of the demo — here with the numbers turned on

每个演示都有多种变体。从下拉菜单中切换变体,这只会改变 CSS。HTML 保持不变。

Screenshot showing the controls of the demo — here with the dropdown of various alternative layouts showing

创建经典的瀑布流/流式布局

首先,让我们看看如何构建经典的瀑布流/流式布局。在这个图片库中,每张图片都用一个 figure 元素包裹,并且这些 figuremain 元素的直接子元素。

<main>
  <figure><img src="photo-1.jpg"></figure>
  <figure><img src="photo-2.jpg"></figure>
  <figure><img src="photo-3.jpg"></figure>
</main>

我们首先对 main 元素应用 display: grid 来创建网格容器。然后我们可以根据需要定义 grid-template-columns

在这种情况下,我们使用 grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)),要求浏览器重复一个循环定义来创建一组灵活的列,每列最小为 14rem。这会产生均匀大小的列,这是经典的瀑布流/流式布局的典型特征。gap: 1rem; 规则在项目之间创建了一个 1rem 宽的间距——包括列之间以及项目之间的水平间距。

然后,我们将使用 masonry 值来定义“行”。(此值的名称在浏览器中发布之前可能会更改——更多内容请参阅本文末尾。目前,masonry 是可行的。)

main { 
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
  gap: 1rem;
  grid-template-rows: masonry;
}

grid-template-rows: masonry 规则告诉浏览器:“请不要创建行。而是将内容打包成瀑布流/流式模式。”

就这样!在四行 CSS 代码中,无需任何媒体查询或容器查询,我们就创建了一个在各种尺寸屏幕上都能工作的灵活布局。而且无需裁剪内容以强制所有内容都放入相同大小的框中。

masonry layout of photos

在平面设计中,具有统一大小列而没有行的布局通常被称为“对称列式网格”。几个世纪以来,列式网格一直是页面设计中主要使用的网格类型。

利用 Grid 的全部能力来定义列

现在,让我们深入探讨结合 CSS Grid 的全部功能与瀑布流/流式排列的优势。CSS Grid 提供了多种选项来定义网格列。使用 fr 单位创建对称网格只是众多选项之一。

这些可能性如何用于瀑布流式布局?让我们尝试将固定大小的列与灵活列混合使用。我们可以将第一列和最后一列固定大小,而中间列灵活,大小和数量都可变。

具体来说,第一列和最后一列的宽度正好是 14 个字符,而中间列是灵活的(至少 28 个字符宽),并根据可用空间改变数量。

main { 
  display: grid;
  grid-template-columns: 14ch repeat(auto-fill, minmax(28ch, 1fr)) 14ch;
  grid-template-rows: masonry;
  gap: 1rem;
}

这只是众多可能性之一。

CSS Grid 提供了丰富的选项来定义网格轨道,从而带来极大的创意空间

  • 以任何单位定义的固定大小(px、em、rem、cqi、lh、ch、ic、cap、vw、svh 等更多
  • max-contentmin-content
  • fr 单位的全部威力
  • minmax() 函数
  • % 大小
  • 自动

CSS Grid 中的这些选项让您能够以有趣的方式创建更具动态性和灵活性的布局。您可以创建两个阶段的灵活性,因为 fr 单位大小的列与 minmax() 大小列的增长和收缩发生在不同的阶段。max-contentmin-content 值允许您根据内容大小来调整列的大小,而不是根据列的大小来调整内容的大小。fr 单位可以轻松用于创建复合或不对称网格,其中列的大小不同。可能性是无限的。

通过在 CSS Grid 中增加以瀑布流模式打包内容的能力,我们得以保持 Grid 的全部功能,以我们喜欢的方式定义列。

例如,让我们使用 grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr) minmax(16rem, 2fr)) minmax(8rem, 1fr) 来创建一种交替的窄列和宽列模式,其中所有列都是灵活的。当有空间时,会添加更多列。而且总是奇数列,以窄列结尾。

three web browser windows next to each other, showing how the layout adjusts for narrow, medium and wider windows

即使我们只使用简单的 fr 单位定义列,CSS Grid 提供的全部功能意味着不同的列可以设置为不同的大小。为了好玩,让我们使用 fr 单位来定义一组列,通过在 grid-template-columns: 1fr 1fr 2fr 3fr 5fr 8fr; 中使用斐波那契数列来注入黄金比例的氛围。

layout of photos where the columns on the left are very narrow, getting bigger and bigger as them move to the right, in a fibonacci sequence

在一个更实际的例子中,当定义列时,我们使用 max-content。基于内容的大小调整是 CSS Grid 一个极其强大的功能。这个巨型菜单布局的演示使用了 grid-template-columns: repeat(auto-fill, minmax(max-content, 30ch)); 来确保每一列都足够大,可以容纳所有链接而不会换行。

a menu with a ton of links, like in a website footer, laid out with Grid Level 3

巨型菜单一直难以编码,尤其是在多种屏幕尺寸下。有了 CSS Grid Level 3,它变得异常简单。几行代码就能创建一个动态布局,在空间允许的情况下,逐个添加和删除列——无需任何媒体/容器查询,并防止换行。

其中许多例子在瀑布流作为独立的显示类型时是无法创建的。关于 display: masonry 的讨论是只允许对称列(彼此大小相同的列),就像今天的多列布局一样。

利用 Grid 的能力让内容跨列

CSS Grid 也允许我们让项目跨越多列。让我们利用这个功能,看看会出现什么有趣的选项。比如,让每第 5 张图片跨两列,而其余图片跨一列。

same photo layout, now with random photos being bigger

如果我们将 wider 类专门应用于宽高比更大的图片,以使这些图片跨越多列,那会怎样呢?我们还可以稍微改变一下样式,将圆角改为直角,并将网格间隙减小到零。这为我们将不同宽高比的照片打包在一起提供了另一种方式。

another layout of image, this time where wider images are wider

我们还尝试将经典的瀑布流/流式布局照片与视图过渡结合起来。当用户点击/轻触任何照片时,它会放大以跨越多列。浏览器会自动动画化过渡。(此演示需要 Safari Technology Preview 192 或更高版本。)

这些照片和巨型菜单演示的变体只是您在列方向上充分利用 CSS Grid 的全部功能,同时关闭行的众多可能性中的一小部分。

列式网格 vs. 模块化网格

如果我们继续实验,会发生什么?让我们抛开“瀑布流”的思维,开始纯粹地将 Grid Level 3 想象成 Grid 的扩展。其核心,CSS Grid Level 3 提供了一种关闭行的机制。它让我们能够创建一个列式网格——一个仅由列组成的网格。

相比之下,模块化网格是一种所有内容都在列和行中对齐的网格。它们在 20 世纪现代主义在平面设计中占据主导地位时开始流行。CSS Grid Level 1 非常擅长制作模块化网格……它就是为此而生。事实上,基于浮动的布局也鼓励了网络上的模块化网格的创建,因为你必须让所有内容都具有相同的高度才能使浮动元素清除。图片需要具有相同的宽高比。文本必须具有相同的长度。这通常通过后端的内容管理系统强制执行策略来实现,或者通过前端的 CSS 截断/裁剪内容来实现。

网站采用这种模块化网格的变体非常常见,这里使用 CSS Grid Level 1 进行布局。

layout of text, in ridged sets of boxes, row after row, all the same height

当然,这个例子过于简单。文章导语没有预览图,显得很空洞。这种统一性如此严格和正式,设计缺乏生机。真实的网站会找到其他方式来为设计注入活力。

但如果布局本身也能提供一些活力和趣味呢?如果我们可以像 CSS Grid 创建模块化网格一样轻松地创建列式网格会发生什么?如果不对内容进行截断,而是让它以它想要的大小呈现,并让布局为内容服务,而不是强迫内容适应布局呢?

一个经典瀑布流/流式布局,包含不同长度的文本,看起来是这样的,用户可以阅读更多关于每篇文章的信息,这已经更具吸引力了。

same article teaser text, this time laid out in columns set with masonry

然而,这仍然有点视觉上的重复。对称的列式网格通常如此。我们需要 CSS Grid 的其余功能来做一些更有趣的事情。让我们把最新的文章做得更大,让它跨四列。其他一些最近的文章可以中等大小,跨两列。而旧的内容更小,只跨一列。

same text, this time laid out with much more dynamic use of 'masonry-style layout plus spanning columns

现在,这些原本视觉上枯燥的文字开始显得相当生动。如果我们在每篇文章中添加图片,它会更加动态。

让我们尝试将图像和文字混合在一个博物馆的网页上。第一个网格项是介绍博物馆的标题,并提供到其他资源的导航。其余内容包括艺术品及其信息:标题、艺术家、年份、媒介、目录号和位置。

因为画作很华丽,所以内容在经典的瀑布流/流式布局中看起来非常棒。

layout of cards, each with a painting and text

让我们看看通过利用 CSS Grid 的另外两个强大功能——子网格和显式放置,我们还能做些什么。

使用子网格和显式放置

CSS Grid Level 2 中由 subgrid 提供的功能非常强大,并且最终得到了大多数浏览器的支持。

我们不将画作的元数据列在单一的左对齐列中,而是看看如何更好地利用可用空间。通过使用 subgrid,我们可以将年份和目录号放在每张卡片的右侧——并将一幅画的数据与另一幅画的相同数据对齐。

close-up of this layout, with the Grid Inspector showing, with lines marking columns showing how subgrid works

通过将此新功能添加到 CSS Grid Level 3,我们可以受益于现有的开发者工具。您现在就可以在 Safari Technology Preview 中使用 Grid Inspector,尝试 grid-template-rows: masonry

如果瀑布流是它自己的显示类型,而不是 CSS Grid 的一部分,它将无法享受子网格的好处。

我们还可以利用 CSS Grid Level 1 的强大功能,通过 grid-column: -3 / -1 将标题显式放置到最后两列,将其移动到页面的右上角。

仅用几行布局代码,我们就可以充分利用 CSS Grid Level 1、2 和 3 的强大功能,创建出可随可用空间改变列数的灵活布局,而无需使用任何媒体查询或容器查询。

希望您能看到将瀑布流/流式布局机制与 CSS Grid 完全结合的优势——它提供了比单独的瀑布流更多的创意可能性。

辩论

现在让我们进入一直在阻碍 CSS 工作组前进的辩论。我们希望网页设计师和开发者能够加入进来(发布到社交媒体,撰写博客文章),表达您对 CSS 应该走向哪个方向的看法。

有些人,包括我们 Apple 的同事,喜欢将“瀑布流”作为 CSS Grid 的一部分。我们相信此功能是扩展 CSS Grid 的一种机制——使其最终能够创建列式网格和模块化网格。我们希望此功能能够与 Grid 的所有其他功能混合使用,包括定义列、轨道跨度、显式放置和子网格的强大选项。

其他人则认为瀑布流应该是一种独立的显示类型。乍一看,用新的显示类型来定义瀑布流可能很有意义。你确实可以在布局类型之间实现整洁的分离。

display: block;
display: inline;
display: flexbox;
display: grid;
display: masonry;

CSS 工作组尚未讨论单独的 Masonry 显示类型的语法将如何工作,但也许它会仿照 多列布局

main { 
  display: masonry;
  columns: 28ch;
}

或者,语法可能会仿照 Grid,但有显著限制

main {
  display: masonry;
  masonry-columns: repeat(5, minmax(28ch, 1fr)); 
                   /* where only one repeating width is allowed */
}

无论哪种方式,很明显,这种选项的倡导者希望瀑布流仅限于对称网格——其中所有列的大小彼此相同。CSS Grid 的其他任何轨道尺寸能力都不会被允许。

将瀑布流设为一种简单且独立的布局类型,可以避免在 Grid 和瀑布流之间保持协同工作所需的长期努力。这样做将简化布局模型,使其在浏览器中更容易实现,减少性能陷阱的可能性,并允许 Grid 和瀑布流的功能集有所区别。

反之,我们相信将此功能添加到 CSS Grid 所需的努力是值得的,它将带来诸多益处。CSS Grid Level 3 规范已经编写完成,并在两个浏览器引擎中实现。是的,虽然使 CSS Grid 更复杂会在未来使其扩展变得更难,但我们认为将这两种网格布局类型交织在一起具有优势。这样,CSS 工作组将始终为模块化和列式网格定义所有新增功能。不会有添加到 display: grid 中而遗漏在 display: masonry 中的内容,反之亦然。例如,许多开发者希望 CSS Grid Level 4 提供一种样式化网格区域和网格线的方法——也许是为轨道添加背景颜色,或者在间隙中创建一条规则线。确保这从第一天起就适用于模块化和列式网格会很棒。

display: masonry 的倡导者提出的另一个论点是,瀑布流在概念上与 CSS Grid 是一种根本不同的布局类型,因此应该有自己的显示类型。他们经常将 CSS Grid 描述为本质上是关于“在二维中对齐事物”,而瀑布流只在“一维中对齐事物”,因此“它不是网格”。(事实上,有些人主张瀑布流更像 Flexbox,因为“两者都在一个方向上对齐事物”。)

在许多方面,您对这个问题的看法可能取决于您想象中的网格是什么。

什么是网格?

网格是平面设计中极其重要的一部分。网格用于将文本、图像和其他内容以规则的模式对齐。它们通过使事物可预测来帮助提高可读性和可用性。

您可以追溯它们数千年的历史。

two photos of book pages, showing historic columnar layouts from long ago
[左] 安托万·维拉德于 1498 年出版了薄伽丘的法文译本。选自詹姆斯·克雷格和布鲁斯·巴顿 1987 年的《平面设计三十世纪》。[右] 一张俄语插图纸宣告“啤酒花超越所有其他水果”。选自马丁·莱昂斯 2011 年的《书籍:活着的历史》。

直到 20 世纪,欧洲和美国的现代主义者才开始推广“正确”的平面设计网格应该在两个方向上(行和列)对齐内容的理念。

spread from a book, showing two different newspaper home pages, where articles line up in a modular grid — in both row and column directions
马西莫·维涅利以倡导在列和行中对齐事物是平面设计中的一种卓越实践而闻名。选自《维涅利典范》2010 年的两个例子。

即使在今天,关于哪种网格是最好的网格或唯一合法的网格仍存在很多争论。许多设计师声称 12 列网格是设计网页的唯一正确方式——或者“桌面”12 列,“平板”8 列,“手机”4 列。有时设计师对他们心目中“正确的网格”的样子非常执着。

马克·布尔顿多年来一直认为对称的列式网格过于公式化和无聊。他提倡在网页设计中使用不对称复合网格。如今,幸运的是 CSS Grid Level 1 使创建不对称网格和复合网格变得异常容易,赋予设计师创作他们想要的自由。但前提是他们也希望所有的网格都是模块化网格。

模块化网格和列式网格实际上都是网格。CSS Grid 理应具备创建列式网格的能力。

photograph of an open book, showing many many hand-drawn diagrams of how different layouts can use different numbers of columns
《设计编辑》中关于设计列式网格的理念,作者:扬·V·怀特。

我们相信 CSS 有机会将丰富的历史设计网格带入网络——如果新的瀑布流功能仅限于允许对称列式网格,我们将感到非常失望。

但您怎么看呢?

我们希望听到您的意见

这就是您发挥作用的地方。亲自尝试一些演示。在您的博客上写下您的想法。在此问题中向 CSS 工作组发表评论

  • “瀑布流”/“流式”布局是否应该成为 CSS Grid 的一部分?
  • 您是否希望 CSS Grid 具备定义列式网格的能力——使用子网格、跨度、显式放置以及所有各种轨道尺寸选项?还是您只希望能够定义具有等宽列的经典瀑布流布局?
  • 你会使用它吗?你会用它创造什么?
  • 您有制作的演示链接吗?我们想看看您的想法和用例。
  • 有没有您想做但用此模型无法做到的事情?

通常,理论上的思考与实际使用情况可能大相径庭。为了确保 CSSWG 正确设计此功能,我们需要开发者获得一些实践经验,并阐明您对此功能的体验感受。

WebKit 团队已经研究瀑布流一年半了。它最初于 2023 年 2 月在 Safari Technology Preview 163 中默认启用。还需要进行一些优化,并解决一些细节问题(命名就是其中之一)。但我们希望尽快发布此功能。为此,这些基本问题需要得到解决。

感谢您的帮助!

附:关于名称

masonry 可能不是这个新值的最佳名称。CSS 中的名称通常是直接描述其结果的简单词语——例如 centerundercontaincliprevertltralwaysbreak-wordhiddenallow-endscale-downwrapsmooth

“masonry”这个词更多的是一个比喻,其含义需要通过背景故事来解释。对于不讲英语的开发者来说,这样的术语更难记住。而且可以说,语法也可以同样轻易地写成 grid-template-rows: waterfall,因为在某些地区,这是这种布局的主导词,而不是 masonry。

此外,一旦您开始大量编写使用此功能的代码,您很可能会意识到我们所意识到的——这真的与 Pinterest 或其他类似网站使用的布局无关。这是一种告诉浏览器“请创建一个网格,但不带任何行”的机制。

也许最好的语法是 grid-template-rows: none; 来传达“请不要给我任何行”。遗憾的是,使用这个名称为时已晚,因为 nonegrid-template-* 的默认值,表示“请只给我隐式行,没有显式行”。

相反,我们可以使用名称 off 来传达“请关闭行方向的网格,只给我列”。

main { 
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
  grid-template-rows: off; 
}

CSSWG 正在此问题中讨论这个名称。如果您对名称有想法或偏好,请加入讨论。

同时,masonry 是 Safari Technology Preview 中当前实现的值,因为这是编辑草案目前使用的值。所以这也是我们在上面的演示中使用的,也是您应该在您的演示中使用的。但请务必预料到此值的名称将来会改变。并且或许为将来我们称之为“columnar grid”或“Grid Level 3”而不是“Masonry”做好准备。