CSS 砌体布局之争:网格扩展、独立模块还是全新 Item Flow?

本文探讨了CSS中实现砌体布局的三种提案:扩展CSS Grid、创建独立模块以及WebKit团队提出的Item Flow方案,分析了各自优缺点及对前端开发的影响。

CSS 中的砌体布局:网格应该进化还是为新模块让路?

曾有两个竞争提案在 CSS 中增加对砌体式布局的支持。一方提议扩展现有的 CSS Grid 规范,另一方则建议将砌体布局设立为独立模块。但最近情况又发生了变化,现在出现了第三个提案——Apple WebKit 的“Item Flow”。前两方都提出了强有力的论点,而第三方则将它们合并为一,本文将会详细介绍这些内容。

如果你需要构建一个 Pinterest 风格的布局,但又厌倦了 JavaScript,CSS 最终能提供解决方案吗?对于初学者来说,看一眼 Pinterest 页面上的图钉,你可能会认为 CSS 网格布局就足够了,但直到开始构建时,你才会意识到仅靠 display: grid 和一些额外调整是远远不够的。事实上,Pinterest 是用 JavaScript 构建其布局的,但如果只用 CSS 该有多酷?如果有一个 CSS display 属性可以在不需要任何额外 JavaScript 的情况下提供这样的布局,那该有多棒?

也许有。CSS 网格布局有一个实验性的 grid-template-rows: masonry 值。砌体布局是一种不规则、流动的网格。不规则之处在于,砌体布局中下一行的项目会上升填充砌体轴上的空间,而不是遵循严格的网格模式并在较短片段后留下空白。这是作品集、图片库和社交信息流的理想选择——这些设计依赖于有机流动。但问题是:虽然这个实验性功能存在(例如在启用标志的 Firefox Nightly 中),但由于有限的浏览器支持和当前形式的一些粗糙之处,它并不是你想象中的无缝解决方案。

也许没有。CSS 缺乏原生砌体支持,迫使开发者使用 hack 或 JavaScript 库(如 Masonry.js)。具有良好设计背景的开发人员对 CSS 网格形式的砌体布局提出了批评,Rachel 指出砌体的有机流动与 Grid 严格的二维结构形成对比,可能会让期望 Grid 类似行为的开发者感到困惑;Ahmad Shadeed 则抱怨它使网格布局变得比应有的更复杂,可能会让重视 Grid 清晰结构布局的开发者不知所措。Geoff 也呼应了 Rachel Andrew 的担忧,即“为了理解砌体行为而教授和学习网格,不必要地将两种不同的格式化上下文混为一谈”,使依赖清晰心智模型的设计师和开发者的教育复杂化。

也许还有希望。Apple WebKit 团队刚刚提出了一个新的竞争者,声称不仅将网格和砌体的优点合并到一个统一的系统简写中,还包括了 flexbox 的概念。想象一下,三个 CSS 布局系统的精华合而为一。

鉴于这些抱怨和批评——以及一个新的参与者——问题是:CSS Grid 应该扩展以处理砌体布局,还是应该由一个全新的专用模块接管,或者应该由 item-flow 来主导?

当前 CSS 中砌体布局的现状

许多开发者尝试使用 CSS Grid 配合手动行跨度 hack、CSS 多列(Columns)和 JavaScript 库来在其 Web 应用程序中创建砌体布局。没有原生砌体支持,开发者通常求助于像这样的 Grid hack:一种 grid-auto-rows 技巧与 JavaScript 结合来模拟流动。它有效——勉强算是——但裂缝很快就会出现。

例如,下面的示例依赖 JavaScript 在渲染后测量每个项目的高度,计算项目应跨度的 10px 行数(加上间隙),动态设置 grid-row-end,并使用事件监听器在页面加载和窗口调整大小时调整布局。

1
2
3
4
5
6
7
<!-- HTML -->
<div class="masonry-grid">
  <div class="masonry-item"><img src="image1.jpg" alt="Image 1"></div>
  <div class="masonry-item"><p>Short text content here.</p></div>
  <div class="masonry-item"><img src="image2.jpg" alt="Image 2"></div>
  <div class="masonry-item"><p>Longer text content that spans multiple lines to show height variation.</p></div>
</div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* CSS */
.masonry-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); /* 响应式列 */
  grid-auto-rows: 10px; /* 小的行高以实现精确跨度 */
  grid-auto-flow: column; /* 从左到右填充列 */
  gap: 10px; /* 项目之间的间距 */
}

.masonry-item {
  /* 确保内容不溢出 */
  overflow: hidden;
}

.masonry-item img {
  width: 100%;
  height: auto;
  display: block;
}

.masonry-item p {
  margin: 0;
  padding: 10px;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// JavaScript

function applyMasonry() {
  const grid = document.querySelector('.masonry-grid');
  const items = grid.querySelectorAll('.masonry-item');

  items.forEach(item => {
    // 重置任何先前的跨度
    item.style.gridRowEnd = 'auto';

    // 根据项目高度计算要跨的行数
    const rowHeight = 10;
    const gap = 10;
    const itemHeight = item.getBoundingClientRect().height;
    const rowSpan = Math.ceil((itemHeight + gap) / (rowHeight + gap));

    // 应用跨度
    item.style.gridRowEnd = `span ${rowSpan}`;
  });
}

// 在加载和调整大小时运行
window.addEventListener('load', applyMasonry);
window.addEventListener('resize', applyMasonry);

这个 Grid hack 让我们接近砌体布局——项目堆叠,间隙填充,看起来足够体面。但说实话:它还没有达到。上面的代码示例,与原生 grid-template-rows: masonry(这是实验性的,仅存在于 Firefox Nightly 中)不同,依赖 JavaScript 来计算跨度,违背了“无 JavaScript”的梦想。JavaScript 逻辑通过在调整大小或内容更改时重新计算跨度来工作。正如 Chris Coyier 在他对类似 hack 的批评中指出的那样,这可能导致复杂页面上的延迟。

此外,逻辑 DOM 顺序可能与视觉流不匹配,这是 Rachel Andrew 对砌体布局普遍提出的担忧。最后,如果图像加载缓慢或内容移动(例如,延迟加载的媒体),则需要重新计算跨度,存在布局跳动的风险。这并不是理想的 hack;我相信你会同意。

开发者需要流畅的体验,从人机工程学角度来说,用脚本 hack Grid 是一种心理杂耍。它迫使你在 CSS 和 JavaScript 之间切换来调整布局。一个原生解决方案,无论是 Grid 驱动的还是一个新模块,都必须实现轻松响应、整洁渲染以及一个不会让你弄坏工具的工作流程。

“这就是为什么这场辩论很重要——我们的日常工作需要它。

选项 1:扩展 CSS Grid 以支持砌体布局

一种前进的方向是增强 CSS Grid 的砌体能力。截至本文撰写时,CSS 网格已被扩展以容纳砌体布局。grid-template-rows: masonry 是 CSS Grid Level 3 的一个草案,目前已在 Firefox Nightly 中进行实验。此布局的列将保持为网格轴,而行则采用砌体布局。然后,子元素沿着行逐个项目进行布局,就像网格布局的自动放置一样。使用这种布局,项目垂直流动,尊重列轨道但不尊重行约束。

此选项让 Grid 作为你的首选布局系统,但允许它处理我们渴望的流动、间隙填充堆栈。

1
2
3
4
5
6
.masonry-grid {
  display: grid;
  gap: 10px;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  grid-template-rows: masonry;
}

首先,grid-masonry 风格建立在 CSS Grid 的熟悉度和强大工具(例如,DevTools 支持)之上。作为前端开发者,你可能已经玩过 grid-template-columnsgrid-area,所以你已经 halfway up the learning matrix。砌体布局只是扩展了现有功能,消除了从头学习全新语法的需要。此外,Grid 的强大工具随之而来,例如 Chrome DevTools 的网格覆盖或 Firefox 的布局检查器,消除了对 JavaScript hack 的需求。

别急:存在局限性。Grid 的规范已经包含了诸如 align-contentgrid-auto-flow 等属性。将砌体布局堆叠在列表中 risks turning it into a labyrinth。

然后还有边缘情况。当你希望一个项目跨越多个列并以砌体样式流动时会发生什么?或者当项目之间的间隙在各列之间不对齐时?规范在这里仍然模糊不清,早期测试暗示了错误,例如如果内容动态加载,项目会不可预测地跳跃。这个问题可能会破坏布局,尤其是在响应式设计上。浏览器兼容性问题也存在。它仍然是实验性的,即使有 polyfills,它也不能在除 Firefox Nightly 以外的其他浏览器上工作。这不是你想在下一个客户项目中尝试的东西,对吧?

选项 2:一个独立的砌体布局模块

如果我们采用 display: masonry 方法呢?请容我几分钟。这不仅仅是一厢情愿的想法。早期的 CSS 工作组讨论已经提出了这个想法,并且值得描绘它如何改进布局。让我们深入探讨这个愿景,它可能如何工作,以及在此过程中的得失。

想象一个不依赖于 Grid 的严格轨道或 Flexbox 的线性流动的布局系统,而是在垂直堆叠中蓬勃发展并带有水平扭曲。目标?为砌体布局的标志性外观提供一个干净的石板:项目级联向下排列,自然地填充间隙,无需 hack。受 CSSWG 讨论中的传闻和 Chrome 团队替代提案的启发,该模块将优先考虑流动性而非结构性,为设计师提供一个感觉像他们追求的布局一样直观的工具。想象一下 Pinterest,但没有 JavaScript 脚手架。

提议是这样的:一个名为 masonry 的 display 值启动一个基于流动的系统,其中项目默认垂直堆叠,水平调整以适应容器。你可以用简单的属性控制方向和间距,如下所示:

1
2
3
4
5
.masonry {
  display: masonry;
  masonry-direction: column;
  gap: 1rem;
}

想要更多控制?假设的额外功能如 masonry-columns: auto 可以模仿 Grid 的 repeat(auto-fill, minmax()),而 masonry-align: balance 可能会均衡列长度以获得抛光的外观。它 less about precise placement (Grid’s strength) and more about letting content breathe and flow, adapting to whatever screen size is thrown at it。这里的巨大胜利是与 Grid 的严格顺序彻底决裂。一个独立的模块使它们保持 distinct: Grid for order, Masonry for flow。不再需要与不太合适的 Grid 属性搏斗;你得到一个为这项工作量身定制的系统。

当然,并非一帆风顺。一个全新的规范意味着从零开始。浏览器供应商需要团结 behind it,这可能很慢。此外,它可能会导致选择困惑,开发者会问这样的问题:“我应该用 Grid 还是 Masonry 来做这个画廊?”但请听我说:这个提议的模块可能会在澄清之前搅浑水,但在水清之后,所有人都可以安全使用。

Item Flow:统一的布局解决方案

2025 年 3 月,Apple 的 WebKit 团队提出了 Item Flow,这是一个新系统,将来自 Flexbox、Grid 和砌体布局的概念统一到一组属性中。Item Flow 不是选择增强 Grid 还是创建新的砌体模块,而是合并了它们的优点,用一个名为 item-flow 的简写替换了 flex-flowgrid-auto-flow。该系统引入了四个长手属性:

  • item-direction: 控制流动方向(例如,row, column, row-reverse)。
  • item-wrap: 管理换行行为(例如,wrap, nowrap, wrap-reverse)。
  • item-pack: 决定打包密度(例如,sparse, dense, balance)。
  • item-slack: 调整布局调整的容差,允许项目收缩或移动以适应。

Item Flow 旨在使砌体布局成为这些属性的自然结果,而不是一个独立的功能。例如,砌体布局可以通过以下方式实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
.container {
  display: grid; /* 或 flex */
  item-flow: column wrap dense;

  /* 长手版本 */
  item-direction: column;
  item-wrap: wrap;
  item-pack: dense;

  gap: 1rem;
}

这种设置允许项目垂直流动,换行成列,并紧密打包,模仿砌体的有机排列。密集打包选项(dense)受 Grid 的 auto-flow: dense 启发,重新排序项目以最小化间隙,而 item-slack 可以微调间距以实现视觉平衡。

Item Flow 的前景在于其广泛的用例。它通过诸如 Grid 的 nowrap 或 Flexbox 的 balance 打包等功能增强了 Grid 和 Flexbox,解决了开发者长期的愿望清单。然而,该提案仍在讨论中,并且像 item-slack 这样的属性由于对非英语母语者的清晰度问题而面临命名争议。

缺点?Item Flow 是一个面向未来的概念,截至 2025 年 4 月尚未在浏览器中实现。开发者必须等待标准化和采用,并且 CSS 工作组仍在收集反馈。

哪条是正确的道路?

虽然这个问题没有直接答案,但砌体布局的辩论取决于在简单性、性能和灵活性之间取得平衡。用砌体扩展 Grid 很诱人,但有可能使一个已经健壮的系统过度复杂化。一个独立的 display: masonry 模块提供了清晰度,但增加了 CSS 的学习曲线。最新的竞争者 Item Flow 提出了一个统一的系统,可以使砌体布局成为 Grid 和 Flexbox 的自然扩展,最终可能平息这场辩论。

每种方法都有权衡:

  • 带砌体的 Grid:熟悉但可能笨拙,存在可访问性和规范问题。
  • 新模块:干净且专为目的构建,但需要学习新语法。
  • Item Flow:优雅且多功能,但尚不可用,命名和实现存在持续争论。

Item Flow 在增强现有布局的同时支持砌体布局的能力使其成为一个引人注目的选项,但其成功取决于浏览器的采用和社区支持。

结论

那么,在这一切之后,我们何去何从?砌体布局的摊牌归结为三条路径:将砌体扩展到 CSS Grid 中,为砌体创建一个独立模块,或者采用 Item Flow。现在的问题是,CSS 最终会让我们摆脱 JavaScript 来实现砌体布局,还是我们仍在做梦?

Grid 用一点味道 teasing 我们,一个独立模块 whispering promises——但终点线 unclear,而 WebKit 用一个杀手级的合并简写 Item Flow 介入。浏览器的支持、社区的推动以及更多的规范修订可能会告诉我们答案。就目前而言,这是你的行动——测试、调整并权衡。答案即将到来,一次一个布局。

参考资料

  • Rachel Andrew 的“Native CSS Masonry Layout in CSS Grid”
  • Ahmad Shadeed 的“Should Masonry be part of CSS Grid?”
  • Geoff Graham 的“CSS Masonry & CSS Grid”
  • Michelle Barker 的“Masonry? In CSS?!”
  • Chris Coyier 的“Native CSS Masonry Layout in CSS Grids”
  • WebKit 的“Item Flow Part 1: A Unified Concept for Layout”
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计