CSS 砌体布局之争:Grid 进化、独立模块还是全新方案?

本文探讨了CSS中实现砌体(Masonry)布局的三种提案:扩展CSS Grid、创建独立模块以及WebKit的Item Flow方案,分析了各自的技术实现、优缺点及开发者面临的实际挑战。

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

目前围绕在 CSS 中添加砌体(Masonry)样式布局的支持,存在多个相互竞争的建议。一方提出扩展现有的 CSS Grid 规范,另一方则建议将砌体布局设立为一个独立的模块。而最近,苹果 WebKit 团队提出了第三个选项——“Item Flow”。前两方各有充分理由,第三方则将它们融合为一,本文将详细介绍这些内容。

如果你需要构建一个类似 Pinterest 风格的布局,但又厌倦了使用 JavaScript,那么 CSS 是否终于能提供解决方案?对于初学者而言,乍看 Pinterest 页面的图钉布局,你可能会认为 CSS 网格布局就足够了,但直到实际构建时才会发现,仅靠 display: grid 加上额外调整是远远不够的。实际上,Pinterest 是使用 JavaScript 构建其布局的,但如果仅用 CSS 就能实现,那该有多酷?如果有一个 CSS 显示属性能够无需任何额外 JavaScript 就提供这样的布局,那该有多棒?

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

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

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

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

当前 CSS 中砌体布局的现状

许多开发者尝试使用 CSS Grid 配合手动行跨度 Hack、CSS 多列(Columns)和 JavaScript 库,在他们的网页应用中创建变通方案来实现砌体布局。没有原生砌体支持,开发者通常转向像这样的 Grid Hack:一种 grid-auto-rows 技巧配合 JavaScript 来模拟流动。它有效——某种程度上——但裂缝很快就会出现。

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

1
2
3
4
5
6
<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
.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
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-砌体风格建立在 CSS Grid 的熟悉度和强大工具(例如,DevTools 支持)之上。作为前端开发者,你可能已经玩过 grid-template-columnsgrid-area,所以你已经 halfway up the learning matrix。砌体布局仅扩展了现有功能,消除了从头学习全新语法的需要。此外,Grid 的强大工具随 Chrome DevTools 的网格覆盖或 Firefox 的布局检查器一起提供,消除了对 JavaScript Hack 的需求。

别太快:存在限制。Grid 的规范已经包含了诸如 align-contentgrid-auto-flow 等属性。将砌体布局堆叠在列表中可能使其变成迷宫。

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

选项 2:独立的砌体模块

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

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

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

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

想要更多控制?假设的额外功能如 masonry-columns: auto 可以模仿 Grid 的 repeat(auto-fill, minmax()),而 masonry-align: balance 可能会平衡列长度以获得 polished look。它 less about precise placement(Grid 的优势)and more about letting content breathe and flow,适应任何屏幕尺寸。这里的巨大胜利是与 Grid 的严格顺序 clean break。一个独立的模块使它们保持 distinct:Grid 用于 order,Masonry 用于 flow。不再与不太合适的 Grid 属性搏斗;你得到一个为工作量身定制的系统。

当然,并非一帆风顺。一个全新的规范意味着从零开始。浏览器供应商需要团结 behind it,这可能很慢。此外,它可能会导致选择 confusion,开发者会问诸如:“我应该使用 Grid 还是 Masonry 来构建这个画廊?”但请听我说:这个提议的模块可能会在澄清之前 muddy the waters,但在水清澈之后,它就可以 safe for use by all and sundry。

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

2025 年 3 月,苹果的 WebKit 团队提出了 Item Flow,这是一个新系统,将 Flexbox、Grid 和砌体的概念统一到一组属性中。与其选择增强 Grid 或创建新的砌体模块,Item Flow 合并了它们的优势,用名为 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;
}

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

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

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

哪条是正确的道路?

虽然没有直接答案,但砌体辩论的关键在于平衡简单性、性能和灵活性。使用砌体扩展 Grid 很诱人,但有可能使已经 robust 的系统 overcomplicating。一个独立的 display: masonry 模块提供了清晰度,但增加了 CSS 的学习曲线。Item Flow,最新的竞争者,提出了一个统一的系统,可以使砌体成为 Grid 和 Flexbox 的自然扩展, potentially putting the debate to rest at last。

每种方法都有权衡:

  • Grid with Masonry:熟悉但 potentially clunky,存在可访问性和规范问题。
  • New Module:干净且 purpose-built,但需要学习新语法。
  • Item Flow:优雅且 versatile 但尚不可用,存在命名和实现的持续辩论。

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

结论

那么,在这一切之后,我们落在哪里?砌体对决归结为三条路径:将砌体扩展到 CSS Grid 中,一个独立的砌体模块,或 Item Flow。现在的问题是,CSS 最终会让我们从 JavaScript 中解放出来实现砌体布局,还是我们仍在做梦?

Grid 用 taste teasing 我们,一个独立的模块 whispering promises——但 finish line unclear,而 WebKit swoops in with a killer merge shorthand, Item Flow。浏览器的支持、社区的推动以及一些更多的规范修订可能会告诉我们答案。就目前而言,这是你的行动——测试、调整和权衡。答案正在到来,一次一个布局。

参考文献

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