瀑布流布局是图片社区、商品列表页面中最常见的排版方式之一。它的核心特征是列与列之间高度不一致,卡片沿着最短的一列往下堆叠,形成参差错落的视觉效果。早期的瀑布流几乎都依赖JavaScript计算每个卡片的位置,如今纯CSS已经能够实现两种成熟方案:一种是基于Grid布局的隐式网格,另一种是基于columns的多栏布局。这篇文章会把两种方案的原理、代码和适用场景都讲透。

瀑布流布局的核心原理是什么
瀑布流的英文叫masonry layout,直译过来就是砌砖布局。想象一下泥瓦匠砌墙的过程:每一块砖都会放在当前最矮的位置,砌完之后墙面自然平整,但砖缝是错开的。瀑布流正是借鉴了这个思路,每个新卡片都会被放置到当前总高度最小的那一列末尾。
理解了这一点,就能明白为什么传统布局方式做不了瀑布流。Flex布局虽然能轻松实现等宽多列,但同一行的flex项目默认会被拉伸到等高,卡片底边永远对齐,做不到错落效果。要打破这种等高约束,就需要让卡片的高度完全由自身内容决定,并且排列规则从按行排列改成按列排列。
纯CSS实现瀑布流的关键,就是找到一种能让卡片“按列填充”且互不干扰高度的机制。CSS Grid的隐式网格和CSS多栏布局恰好都具备这个特性,下面分别展开。
方案一:用Grid实现精准排列的瀑布流
Grid方案的思路是声明列的数量但不固定行高,让每张卡片占据网格中一个自动计算的行。配合grid-auto-rows设置一个基础行高,再用span控制每张卡片跨多少个基础行,就能模拟出不同高度的卡片在垂直方向上自然堆叠的效果。
先看基础结构,假设容器里有若干高度不一的卡片:
<div class="masonry"> <div class="card">卡片一</div> <div class="card">卡片二</div> <div class="card">卡片三</div> <div class="card">卡片四</div> <div class="card">卡片五</div> </div>
基础样式只需要几行就能跑起来:
.masonry {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 16px;
}
.card {
/* 不设置高度,由内容撑开 */
}这段代码的问题在于Grid默认按行放置项目,同一行的卡片会被拉伸到等高,看起来和普通网格没区别。解决方法是引入grid-auto-rows加span的技巧。把基础行高设成一个较小的值,比如8像素,然后让每张卡片跨越多行。跨多少行合适?如果卡片高度是动态的,纯CSS算不出来,需要借助一个不太常见的属性grid-auto-flow配合密集填充模式:
.masonry {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
grid-auto-rows: 8px; /* 基础行高设小 */
gap: 0 16px; /* 行间距由span内的预留抵消 */
}
.card {
grid-row: span 20; /* 每张卡片跨20个基础行,约160px */
margin-bottom: 16px; /* 用margin代替行gap */
}这种写法要求卡片高度是分段的、可预估的,如果卡片高度完全随机,就需要借助ResizeObserver动态计算span值。虽然用了一点JavaScript,但只是读取高度并设置行跨度,不涉及定位计算,性能远好于传统的绝对定位方案:
const cards = document.querySelectorAll('.card');
const baseRow = 8;
const gap = 16;
const observer = new ResizeObserver(entries => {
for (let entry of entries) {
const card = entry.target;
// 高度除以基础行高,向上取整,再预留一个间隙的行数
const span = Math.ceil((card.offsetHeight + gap) / baseRow);
card.style.gridRowEnd = 'span ' + span;
}
});
cards.forEach(card => observer.observe(card));Grid方案最大的优势是排列顺序符合阅读习惯,卡片从左到右、从上到下依次放置,用户看到的顺序和数据顺序一致。这一点对内容流、商品列表这类顺序敏感的场景非常重要。另外它对动态内容的适应性也好,图片加载完成、字体渲染导致的尺寸变化都会被ResizeObserver捕获并自动修正。
方案二:用columns实现零脚本的瀑布流
columns本来是为报纸分栏排版设计的属性,它会先把内容纵向填满一列,再开启新的一列。这个填充顺序恰好就是瀑布流需要的按列排列,所以实现起来出奇地简单:
.masonry {
column-count: 4; /* 固定4列 */
column-gap: 16px; /* 列间距 */
}
.card {
break-inside: avoid; /* 关键:防止卡片被拆到两列 */
margin-bottom: 16px;
}其中break-inside: avoid是最关键的一行。多栏布局默认会把内容像流水一样在列间切分,一张较长的卡片可能上半截在第一列、下半截在第二列。设置了这个属性后,浏览器会保证卡片完整性,遇到放不下的卡片就整体挪到下一列。
响应式方面,可以用column-width替代固定列数,让浏览器自动根据容器宽度决定列数:
.masonry {
column-width: 220px; /* 期望的列宽 */
column-gap: 16px;
/* 不写column-count,列数自适应 */
}columns方案的缺点也很明显:排列顺序是先纵后横。第一个卡片和第五个卡片在同一列,第二列排在所有第一列内容之后。对于国内用户习惯从左往右扫视的浏览方式,这种顺序错位可能影响信息获取效率,做信息流时需要慎重考虑。
另外还有一个滚动加载的坑需要注意。如果通过接口分页追加新卡片,直接往容器里插入元素,columns会重新平衡所有列,页面上的卡片会跳动甚至换列,用户体验很差。缓解办法是尽量加大每页的数据量,减少追加次数,或者改用Grid方案。
两种方案对比与选型建议
下面把两种方案的关键差异整理成表格,方便对照选择:
| 对比维度 | Grid方案 | columns方案 |
|---|---|---|
| 实现复杂度 | 需要JS配合计算行跨度 | 纯CSS,两三行核心代码 |
| 视觉顺序 | 从左到右,符合阅读习惯 | 先纵后横,顺序错位 |
| 动态追加内容 | 追加在末尾,不影响已有卡片 | 全列重新平衡,卡片会跳动 |
| 浏览器兼容 | 良好,主流浏览器全支持 | 良好,需注意break-inside兼容性 |
| 适用场景 | 内容流、商品列表等顺序敏感页面 | 图片墙、相册等以浏览为主的页面 |
实际选型时可以问自己两个问题。第一,用户是否在意卡片的先后顺序?如果答案是肯定的,比如商品列表里靠前的商品应该出现在视野上方,那就选Grid方案。第二,页面是否需要频繁追加数据?无限滚动场景下columns的重新平衡问题很难根治,Grid是更稳妥的选择。
还有一种思路是把两种方案结合:桌面端用Grid保证顺序,移动端单列展示时退化成普通流式布局,通过媒体查询切换即可。对于图片比例固定、高度可预估的卡片,甚至可以完全省掉JavaScript,直接在渲染时根据图片宽高比算好grid-row的跨度,用服务端模板输出内联样式,性能和体验都能兼顾。
总结一下,纯CSS做瀑布流不再是技术难题。columns方案胜在简单,几行代码就能上线;Grid方案胜在可控,顺序、间距、动态加载都更符合生产环境的要求。根据项目的实际需求选择对应方案,必要时用少量JavaScript补足高度计算的缺口,就能得到一个既好看又好用的瀑布流页面。
CSS瀑布流布局Grid布局columns多栏布局修改时间:2026-09-03 11:47:17