做前端这么多年,要说哪个布局需求最让人头疼,瀑布流绝对排得上号。Pinterest 的纯图片流、小红书的双列信息流、电商平台的商品橱窗,想要那种高度参差、错落有致的排列,又不想让性能打折扣,过去基本只能靠 JS 计算高度,或者直接开一个 Masonry 库硬扛。CSS 的 grid、flex 虽然已经很强,但在瀑布流这块一直缺一个第一方标准答案,直到 grid-template-rows 开始支持 masonry 这个值,事情才真正有了转机。这篇文章我会把做瀑布流这些年踩过的坑、原生方案的语法拆解、降级写法和最终选型建议一次性聊清楚,给需要做瀑布流的朋友一份能直接抄作业的实践文档。
1. 为什么瀑布流让前端头疼了这么多年
1.1 瀑布流的真实需求:不是简单“不等高”
很多人以为瀑布流就是把卡片做成不同高度、错开排列就行。真做起来才会发现,难点根本不在“卡片高度不同”,而在“列与列之间不能留大白缝”。想象一下四根柱子,每个 item 依次往当前最矮的那根柱子上放,新卡片永远补进最短的列,最终四列高度尽量接近,这才是瀑布流最理想的形态。这个“自动找最短列”的动作,恰恰是传统 CSS 布局很难自动做到的。
如果只是把 item 用 float 或者 inline-block 硬排,视觉上会出现大量锯齿状空白,电脑上看着还行,手机上一屏就露馅。所以瀑布流从不只是一个“样式”问题,它本质上是“浏览器如何动态计算每个元素位置”的布局算法问题,这也是为什么它长期被归类为“JS 的活”。
1.2 传统方案各有什么坑
要理解原生的好处,先把过去几年主流的实现挨个复盘一遍,后面用新特性的时候也更容易看出差异。我按“从老到新”的顺序盘一下:
- 绝对定位 + JS 计算:以 Masonry.js 为代表的老牌方案,能精确控制每个卡片位置,但问题也很刺眼。卡片插入删除要重算、窗口 resize 要重算、图片加载完还得重算。内容一多,性能就是无底洞,尤其移动端低端机,滚动起来很容易卡成 PPT。再加上布局计算和渲染线程都在主线程里跑,无限滚动场景下更难受。
- CSS columns 多列布局:columns 最早是给报纸排版用的,把内容分成一条条纵列,元素按竖直方向先填满第一列,再进第二列。用在卡片流上,只需要给卡片加
break-inside: avoid防止截断。兼容性好、代码量极少,但致命伤是阅读顺序变成纵向:如果卡片有明确的先后逻辑,比如时间线、排行榜,用户从左往右读的时候顺序就乱了。另外列的高度平衡不受控制,最后几列容易出现长短不齐。 - 固定行高的 CSS Grid:Grid 能把卡片整齐地塞进网格,但默认每一行高度统一,想要不等高,得给每个 item 手动定义
grid-row: span n。难点在于:内容动态、卡片高度不固定时,写 CSS 的时候根本不知道它应该跨几行。写死 span 只适合设计稿完全可控的场景,一旦数据换成用户生成内容,立刻歇菜。
1.3 三个核心痛点
盘点完传统方案,会发现它们绕不开三个痛点:
- 顺序和性能不可兼得。想要从左到右的阅读顺序,必须上 JS;想用 columns 保性能,阅读顺序就被迫变成纵向。
- 动态内容是噩梦。后端返回的数据高度不确定,图片加载完成后列高变化,整个布局就要重排,开发者只能靠“加载完再初始化”这种 hack 兜底。
- 维护成本高。Masonry 库虽然成熟,但库本身有体积,更新要跟版本走,设计师随手改个间距、列数,前端就要同步改一堆配置。
这也是我一直关注原生 CSS masonry 的原因:标准一旦落地,这些问题直接在浏览器层面被消化掉。虽然现在还是实验特性,但它的设计思路已经非常清晰了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 原生 CSS masonry 的设计思路与语法拆解
2.1 一句话理解 masonry
masonry 不是新发明的一套东西,它被定义在 CSS Grid Layout Module Level 3 里,本质上是 Grid 的一种特殊排列模式。普通 Grid 要求每一行高度一致,而 masonry 模式允许各行自动伸缩,子项在主轴方向一个挨一个挤进去,缺口位置自动留给后面的卡片去填。你可以把它理解为:“Grid 负责划出几根列轨道,masonry 负责在轨道里智能流式填充。”
这种设计最大的好处是,你不用给每个卡片指定位置,浏览器自己就能算出哪一列最短、该把新卡片放哪。对开发者来说,只关心“容器”和“间距”,不用管“每一个卡片”。
2.2 核心语法:grid-template-rows 的一个新值
最核心的写法只需要三五行:
css复制.masonry {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: masonry;
gap: 16px;
}
关键就是 grid-template-rows: masonry。这行代码告诉浏览器:行轴走 masonry 模式,列轴保持普通网格轨道。于是列数确定,行高不再统一,卡片高度由内容决定,浏览器自动把每个 item 放到当前最合适的位置。
如果你想做横向瀑布流,反过来声明即可,也就是 grid-template-columns: masonry;,容器会变成横向滚动瀑布流。这种场景在移动端做横滑翻页时很实用。
2.3 masonry-auto-flow 的 next 与 defined
masonry-auto-flow 是配合 masonry 的自动放置属性,类似 grid 里的 grid-auto-flow,它控制默认放置顺序。默认值和最常用的是 next:
css复制.masonry {
grid-template-rows: masonry;
masonry-auto-flow: next;
}
next 的含义严格按 DOM 顺序流式放置,先尽量把当前可用位置填满,再去下一行,视觉上和常见的瀑布流一致。另一种是 defined,适合你显式指定了部分卡片位置的场景:
css复制.card--large {
grid-row: span 2;
}
.masonry {
grid-template-rows: masonry;
masonry-auto-flow: defined;
}
defined 会优先把有明确位置定义的卡片放好,再自动排放剩余项,逻辑上类似 grid-auto-flow: dense,但更可控。实际项目里,next 用到 95% 的场景,defined 更多是为特殊卡片尺寸准备的。
2.4 轨道对齐控制:align-tracks 与 justify-tracks
细节知道的人不多,但值得了解。masonry 模式下,align-tracks 可以控制跨轨道的对齐方式,justify-tracks 控制行轴轨道内的对齐方式。简单场景用默认值就行,想让所有卡片在各自列内部顶部对齐,保持默认 start 效果就很好;想整体居中,可以这样设置:
css复制.masonry {
align-tracks: center;
}
注意,这俩属性目前只在 masonry 容器里有意义,标准还在完善,生产环境不用纠结,知道有这两把调节杠杆就行。
3. 完整实操:从零搭建一个瀑布流页面
3.1 先确定 HTML 结构
不管最后选哪种降级方案,HTML 保持最干净的单一容器加多个 item 就行:
html复制<div class="masonry">
<article class="card">
<img src="https://example.com/img1.jpg" alt="示例图片" width="400" height="300" loading="lazy">
<div class="card__body">
<h3>卡片标题</h3>
<p>卡片描述文字,高度不固定,这才是瀑布流的关键。</p>
</div>
</article>
<!-- 重复 N 个 .card -->
</div>
不建议在 item 外层再包列容器。一旦包了列容器,其实就是 flex 分列布局了,虽然视觉上也是瀑布流,但列内偏移、顺序控制都会变复杂,而且失去了 CSS masonry 自动填充的意义。让容器直接处理所有 item,是唯一简单且可持续的结构。
3.2 用 columns 作为兼容基线
当前完整支持 masonry 的浏览器几乎为零,所以第一步是用 columns 给你一个在所有浏览器里都能跑的“最低配瀑布流”:
css复制:root {
--gutter: 16px;
}
.masonry {
columns: 260px;
column-gap: var(--gutter);
}
.masonry .card {
break-inside: avoid;
margin-bottom: var(--gutter);
}
columns: 260px 表示每列最小宽度 260px,浏览器根据容器宽度自动计算能放几列。break-inside: avoid 保证卡片不会被截成两半,margin-bottom 给卡片之间补纵向间距,因为 columns 只处理列间间距。这套代码在 IE11 之后的几乎所有浏览器里都能跑,也是 Pinterest 这类图片站生产环境验证过的路子。
3.3 用 @supports 做渐进增强
接下来是重头戏:在真正支持 masonry 的浏览器里,用 @supports 切换到原生模式:
css复制@supports (grid-template-rows: masonry) {
.masonry {
columns: auto;
column-gap: normal;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
grid-template-rows: masonry;
gap: var(--gutter);
}
.masonry .card {
margin-bottom: 0;
}
}
这里有三个细节必须注意。
第一,columns: auto; column-gap: normal; 是为了清掉 columns 基线的列相关设置,否则部分浏览器下两套布局体系会打架。
第二,display: grid 被包在 @supports 里,所以只有支持 masonry 的浏览器才会走到这条路,而能支持 masonry 的浏览器必然也支持 grid,安全性是成立的。不支持 grid 的古老浏览器继续走 columns,互不干扰。
第三,card 上的 margin-bottom 要归零,因为 gap 已经接管行列间距,留着就是双重间距。
这样处理之后,Chrome、Safari、微信内置浏览器以及绝大多数现代浏览器走 columns,Firefox 开 flag 后走原生 masonry,同一套 HTML 完全不用动。
3.4 图片占位与懒加载
瀑布流的坑往往不在布局,而在“图片还没加载完布局就开始算了”。columns 方案里图片加载前后高度不同,会导致卡片跳动、列重排。建议给图片提前声明宽高:
css复制.masonry .card img {
display: block;
width: 100%;
height: auto;
aspect-ratio: attr(width) / attr(height);
}
配合 HTML 里已有的 width="400" height="300" 属性,浏览器在图片加载前就能预留出正确的高度比例,能大幅减少布局偏移(CLS)。懒加载直接用 HTML 属性 loading="lazy" 就行,不需要额外 JS 判断。
3.5 响应式列数的切换策略
固定 4 列在手机上一屏只能看到半张卡,体验非常差。响应式可以用媒体查询切换列数:
css复制@media (max-width: 768px) {
.masonry {
grid-template-columns: repeat(2, 1fr);
columns: 2;
}
}
这里我同时改 columns 和 grid-template-columns,是因为两种模式都需要同步列数。用自定义属性记录列数会更优雅,但生产项目里最简单的就是媒体查询里写两遍。注意 columns 里写成 columns: 2 而不是 columns: 260px,否则手机屏幕上可能还是尝试塞更多列。
4. 兼容性与性能优化
4.1 各大浏览器现状
截至写这篇文章,CSS Grid Layout Module Level 3 的 masonry 值在 W3C 依然处于 Working Draft 状态,不是正式推荐标准。浏览器支持情况一句话概括:Firefox 有实验实现,Chrome 和 Safari 都还没有默认实现。所以想在生产环境直接铺开,目前还不行,这也是前面把 columns 作为必选基线的根本原因。
Firefox 的 flag 名称是 layout.css.grid-template-masonry-value.enabled,在地址栏输入 about:config 就能搜到。Chrome 那边也做过实验性实现,但一直没有默认开放,我个人的判断是距离稳定支持还需要一段时间。不过这不影响我们提前了解并准备降级方案,CSS 的特性从来都是渐进增强的。
4.2 Firefox 里开启实验体验
给想尝鲜的朋友列一下具体步骤:
- 打开 Firefox 地址栏,输入
about:config,回车。 - 搜索
layout.css.grid-template-masonry-value.enabled。 - 双击把它设为
true。 - 重启浏览器,再访问写有
grid-template-rows: masonry的页面即可看到效果。 - 如果 flag 名称在个别版本里变了,直接去 MDN 的
grid-template-rows文档页找最新说明。
开启之后,Firefox 的 performance 也值得测测:在大量卡片场景下,原生 masonry 的滚动流畅度明显比 columns 方案更好,因为浏览器内部做了更好的增量布局,这是 polyfill 永远追不上的。
4.3 性能优化的三个注意点
先说 content-visibility,这个属性和瀑布流是绝配:
css复制.masonry .card {
content-visibility: auto;
contain-intrinsic-size: auto 300px;
}
它可以让视口外的卡片跳过渲染,长列表尤其明显,能省下大量样式计算时间。然后是图片懒加载和宽高预留,前面已经讲了,不再重复。
最后是无限滚动。实时上新卡片时,masonry 模式插入新节点,浏览器要重排整个容器吗?答案是 columns 和 masonry 都有重排开销,但比 JS 绝对定位方案小得多。我习惯在滚动触底时先把新内容拼到一个隐藏占位容器里,计算好内容大小再一次性插入 DOM,这样能明显减轻布局抖动。这算是我做信息流时比较偏爱的一招,分享给各位。
5. 常见问题与排查实录
5.1 为什么 masonry 没有生效
最常见的原因有两个。一是浏览器不支持,Chrome 里写 grid-template-rows: masonry 不会报错,但会把它当普通 grid 处理,行高全部一致,看起来完全不像瀑布流。二是写错轴了,grid-template-columns: masonry 和 grid-template-rows: masonry 千万别搞混,前者跑出来的布局方向完全不对。排查时先在 DevTools 里看样式是否被浏览器接受,再确认当前浏览器是否开启对应实验特性。
5.2 阅读顺序乱了
columns 方案唯一的软肋就是顺序。如果产品要求严格按从左到右、从上到下的时间线展示卡片,columns 不满足,这时需要选择原生 masonry 或 JS 方案。不过也要提醒一句:瀑布流场景本身顺序就弱,商品橱窗、图片浏览、灵感流,用户基本不会严格按序号读。
当然,如果产品经理明确要求按时间排序,就别硬撑了,该上 JS 上 JS,技术选型要为业务准确性服务。
5.3 列间距和行间距对不上
columns 下的行间距是 margin-bottom,列间距是 column-gap;masonry 下统一是 gap。如果你在 columns 和 masonry 之间来回切换,发现间距突然翻倍,多半就是 margin-bottom 忘了在 @supports 里归零,这类问题很好定位:打开 DevTools 看 .card 是否继承了 margin,一眼就能看出来。
5.4 什么时候该放弃纯 CSS
我自己的判断标准是看两个维度。第一,单屏卡片超过 100 个且需要复杂排序,这时 JS 方案的可控性更值钱。第二,有大量跨列和特殊尺寸需求,需要设计模拟“砖墙”的密铺效果,原生 masonry 的自动填充还不够聪明,用 JS 库或者手写瀑布流算法更合适。其余八九成场景,columns 加 masonry 的渐进增强已经足够。
6. 我的选型建议与最终体会
6.1 方案对比速查表
| 方案 | 阅读顺序 | 兼容性 | 性能 | 维护成本 | 适用场景 |
|---|---|---|---|---|---|
| JS 绝对定位(Masonry.js) | 从左到右 | 全浏览器 | 内容多时压力大 | 高 | 富交互、复杂排序 |
| CSS columns | 从上到下 | 全浏览器 | 优秀 | 低 | 图片流、商品橱窗 |
| 固定行高 Grid | 从左到右 | 全浏览器 | 优秀 | 中 | 卡片高度可控的页面 |
| 原生 CSS masonry | 从左到右 | 实验阶段 | 最优 | 极低 | 未来的主流方案 |
6.2 我的建议
如果今天就要上线一个瀑布流页面,我的首选是 columns 基线加 @supports 原生 masonry 增强,既照顾了所有用户,又让支持新标准的浏览器用户获得最好体验。如果产品逻辑允许图片流不强调顺序,直接 columns 一把梭,代码量最少,也没必要等到 masonry 正式落地。如果项目明确要求时间线顺序且内容动态变化大,那就老实上 JS 库,不要为了炫技牺牲业务准确性。
最后说点个人的真实体会。我最早做瀑布流用的是 jQuery 加绝对定位,后来换成 Masonry.js,再后来发现纯 columns 在多数场景完全够用,现在开始用 @supports 平滑过渡到原生 masonry。这一路最大的感受是:布局方案的演进,最后都会收敛到“浏览器原生能力”。标准还差最后一公里,但方向已经非常清晰。看到 grid-template-rows: masonry 出现的瞬间,我第一反应是“终于不用在工程里塞那几百行布局 JS 了”。要是你现在还在犹豫要不要了解它,我的建议是:可以不用,但不能不会。等到 Chrome、Safari 都默认支持的那一天,这项技能就是前端必须掌握的基础能力,而不是什么“新特性”了。
