
摘要
随着网页设计越来越复杂,尤其是图片墙、商品展示、内容卡片这类页面,瀑布流布局(也叫 Masonry 布局)成了非常受欢迎的设计方案。传统实现往往依赖大量 JavaScript 或第三方库,但其实利用 CSS Grid 加上少量 JS,就能实现既响应式又性能优越的瀑布流布局。
本文将深入拆解这种方法的原理,带你一步步理解关键代码的作用,配合可运行的示例,让你能轻松上手并灵活应用到实际项目中。
引言
你有没有注意到 Pinterest、淘宝、微博这样的网站,他们的内容区往往是"砖块"式的排列,且高度不一致,却能做到看起来紧凑、自然,不留大空白?这就是瀑布流布局。
早期,瀑布流多用 JavaScript 计算每个元素的位置,或者用专门的 Masonry.js 库,虽然功能强大,但有时会带来性能负担,且对响应式适配不够友好。
而现代 CSS Grid 给了我们强大的布局能力。只要结合 grid-auto-rows
设定行高基准和 JS 动态计算跨行数,就能用纯 CSS + 少量 JS 实现性能优、响应式友好的瀑布流效果。
本文将以实用示例为核心,逐步深入代码细节,帮助你彻底掌握这套方法。
CSS Grid 实现瀑布流的核心思路
为什么用 CSS Grid?
- CSS Grid 本身支持二维布局(行+列),天生适合网格排列
- 可设置列数自动适应容器宽度,实现响应式
- 结合
grid-auto-rows
可做出"网格行高基准",方便动态跨行 - 省去手动计算每个元素位置的复杂逻辑
主要技术点
-
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr))
这个属性意思是"根据容器宽度自动创建列,列宽最小200px,最大自动撑满",所以列数会随屏幕宽度变。 -
grid-auto-rows: 10px
设定每行的高度为 10px。为什么 10px?这是给 JS 计算跨行数一个基准单位。 -
grid-auto-flow: dense
告诉浏览器紧凑排列,遇到空隙就尽量填满,避免断层。 -
grid-row-end: span X
让每个元素根据自身高度跨越多行,实现高低不一的布局。
需要 JS 的原因
纯 CSS 无法动态根据内容高度调整跨行数,因为 CSS Grid 只能接受固定或预定义的跨行数。必须用 JS 获取元素高度,计算跨几行,然后用 CSS 变量传递给 grid-row-end
。
示例代码详解
HTML 结构
HTML 很简单,就是一个父容器 .container
,里面有一堆内容块 .item
:
html
<div class="container">
<div class="item">内容1</div>
<div class="item">内容2</div>
<div class="item">内容3</div>
<div class="item">内容4</div>
<div class="item">内容5</div>
<div class="item">内容6</div>
</div>
.item
可以是任意内容,比如图片、文字卡片、商品展示块等。
CSS 样式详解
css
.container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); /* 自动适应列数 */
grid-auto-rows: 10px; /* 设定每行高度基准为10px */
grid-auto-flow: dense; /* 紧凑排列,填满空隙 */
gap: 10px; /* 网格间距 */
padding: 10px;
background: #f5f5f5;
}
.item {
background: white;
border-radius: 6px;
padding: 10px;
box-sizing: border-box;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
/* 动态跨行 */
grid-row-end: span var(--row-span, 1);
}
/* 通过伪选择器制造高度差异,模拟真实内容 */
.item:nth-child(odd) {
min-height: 80px;
}
.item:nth-child(even) {
min-height: 150px;
}
重点说明:
-
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr))
会根据父容器宽度自动决定列数,列宽不小于200px,最大填满父容器剩余空间。这样能自适应桌面、平板、手机各种屏幕。 -
grid-auto-rows: 10px
设定了基础行高是10px。为啥不直接用更大数值?用小的基准值方便 JS 计算更加精确。 -
grid-auto-flow: dense
保证"碎片"尽量填充满网格空白,不会留下难看的空隙。 -
grid-row-end: span var(--row-span, 1)
通过 CSS 变量--row-span
控制每个元素跨几行,实现不同高度的块在网格中的合理摆放。
JavaScript 详解
js
const container = document.querySelector('.container');
const rowHeight = 10; // 必须和 CSS 中 grid-auto-rows 保持一致
const gap = 10; // 必须和 CSS 中 gap 保持一致
function resizeAllGridItems() {
const allItems = container.querySelectorAll('.item');
allItems.forEach(item => {
// 获取元素实际高度(包含padding和border)
const height = item.getBoundingClientRect().height;
/*
计算跨行数:
由于每行高度是 rowHeight + gap(行高加行间距),
元素的高度 + gap 除以这个值,得到需要跨多少行
Math.ceil 向上取整,避免内容被截断
*/
const rowSpan = Math.ceil((height + gap) / (rowHeight + gap));
// 通过 CSS 变量传给 grid-row-end 控制跨行数
item.style.setProperty('--row-span', rowSpan);
});
}
// 页面加载时计算一次
window.addEventListener('load', resizeAllGridItems);
// 窗口大小变化时重新计算,保证响应式
window.addEventListener('resize', resizeAllGridItems);
JS 核心点:
- 用
getBoundingClientRect().height
取到元素真实渲染高度 - 用
Math.ceil((height + gap) / (rowHeight + gap))
计算跨行数 - 通过 CSS 变量设置跨行数,让 CSS Grid 能正确渲染布局
- 绑定到
load
和resize
事件,动态响应不同屏幕尺寸和内容变更
实际场景举例
图片墙
html
<div class="container">
<img class="item" src="img1.jpg" alt="图片1" />
<img class="item" src="img2.jpg" alt="图片2" />
<img class="item" src="img3.jpg" alt="图片3" />
</div>
css
.item {
width: 100%; /* 图片宽度撑满格子宽 */
object-fit: cover;
border-radius: 6px;
display: block;
grid-row-end: span var(--row-span, 1);
}
JS 同理,动态计算每张图片的高度,设置跨行数。
效果:图片尺寸各异但排列紧凑,不留空白。
商品列表
html
<div class="container">
<div class="item">
<img src="product1.jpg" />
<h3>商品名A</h3>
<p>详细描述内容,可以有长短不一的文字。</p>
</div>
<div class="item">
<img src="product2.jpg" />
<h3>商品名B</h3>
<p>较短的描述。</p>
</div>
</div>
图片和文字高度不固定,JS 自动根据内容撑开高度,计算跨行数,保证整齐排列。
博客文章卡片
html
<div class="container">
<div class="item">
<h2>标题1</h2>
<p>摘要文字,长度不定...</p>
</div>
<div class="item">
<h2>标题2</h2>
<p>更长的摘要文字示例,这里会撑开卡片高度...</p>
</div>
</div>
同样适用该瀑布流方案,保证不同字数卡片排列自然。
深入代码理解与优化
为什么 grid-auto-rows
要和 JS 计算严格对应?
grid-auto-rows
定义了每一行的"高度单位",比如 10px,这个数值直接影响 JS 计算跨行数的基准。如果两者不匹配,布局就会错位。
例如:
- CSS 定义:
grid-auto-rows: 10px;
- JS 计算跨行数公式:
rowSpan = Math.ceil((height + gap) / (rowHeight + gap))
- 这里
rowHeight
必须和 CSS 中的 10px 一致,gap
同理。
如果不一致,内容块会错跨行,出现空隙或重叠。
为什么计算公式要加上 gap
?
因为每个网格单元格之间有间距 gap
,比如 10px。如果不加这个间距,元素高度除以单行高度会偏小,导致布局不准确。
加上 gap 后,计算更精确:
js
const rowSpan = Math.ceil((height + gap) / (rowHeight + gap));
grid-auto-flow: dense
的作用
dense
让布局尽可能填满空隙,比如当一个内容块比较矮的时候,它会往前挤,填补前面因为高块留下的空白,不留空隙。
缺点是可能会打乱原本元素的排列顺序,不过大多数瀑布流场景这是可接受的。
响应式适配原理
使用 repeat(auto-fill, minmax(200px, 1fr))
,列数自动适配屏幕宽度:
- 大屏幕时列数多,每列至少200px宽
- 小屏幕时列数减少,自动缩放到一列或两列
- 无需额外 JS 处理列数变化,纯 CSS 实现
性能优化建议
- 只在必要时调用 JS 计算,比如页面加载和窗口尺寸改变时
- 计算前判断元素是否可见,避免不必要计算
- 结合
requestAnimationFrame
优化 resize 事件响应 - 对于大量内容,考虑分页加载或虚拟滚动,减轻计算压力
完整示例:可直接复制运行
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>CSS Grid 瀑布流示例</title>
<style>
body {
margin: 0;
font-family: Arial, sans-serif;
background: #eee;
}
.container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
grid-auto-rows: 12px;
grid-auto-flow: dense;
gap: 12px;
padding: 12px;
max-width: 1200px;
margin: 0 auto;
}
.item {
background: white;
border-radius: 8px;
padding: 15px;
box-sizing: border-box;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
grid-row-end: span var(--row-span, 1);
}
.item img {
width: 100%;
height: auto;
border-radius: 6px;
display: block;
margin-bottom: 8px;
}
</style>
</head>
<body>
<div class="container" id="grid">
<div class="item">
<img src="https://picsum.photos/300/200?random=1" alt="随机图片1" />
<h3>标题1</h3>
<p>这是一段简介文字,可以长也可以短。</p>
</div>
<div class="item">
<img src="https://picsum.photos/300/350?random=2" alt="随机图片2" />
<h3>标题2</h3>
<p>描述内容更长一些,撑高卡片高度测试。</p>
</div>
<div class="item">
<img src="https://picsum.photos/300/150?random=3" alt="随机图片3" />
<h3>标题3</h3>
<p>短内容。</p>
</div>
<div class="item">
<img src="https://picsum.photos/300/400?random=4" alt="随机图片4" />
<h3>标题4</h3>
<p>更多内容测试,看看效果如何。</p>
</div>
<div class="item">
<img src="https://picsum.photos/300/250?random=5" alt="随机图片5" />
<h3>标题5</h3>
<p>适合博客、商品或图片展示。</p>
</div>
<div class="item">
<img src="https://picsum.photos/300/300?random=6" alt="随机图片6" />
<h3>标题6</h3>
<p>内容随意,卡片高度自然变化。</p>
</div>
</div>
<script>
const container = document.getElementById('grid');
const rowHeight = 12; // 要和 grid-auto-rows 保持一致
const gap = 12; // 要和 gap 保持一致
function resizeAllGridItems() {
const allItems = container.querySelectorAll('.item');
allItems.forEach(item => {
const height = item.getBoundingClientRect().height;
const rowSpan = Math.ceil((height + gap) / (rowHeight + gap));
item.style.setProperty('--row-span', rowSpan);
});
}
window.addEventListener('load', resizeAllGridItems);
window.addEventListener('resize', () => {
// 防抖优化
clearTimeout(window._resizeTimer);
window._resizeTimer = setTimeout(resizeAllGridItems, 100);
});
</script>
</body>
</html>
QA 环节(再深入一点)
Q1:如果内容动态变化,比如异步加载图片怎么办? A1:图片加载完成后会影响元素高度,所以需要监听图片加载事件,或者在所有图片加载完成后调用 resizeAllGridItems()
,确保布局正确。
Q2:支持 IE 浏览器吗? A2:CSS Grid 需要 IE11+ 支持,低版本浏览器不支持。grid-auto-flow: dense
在 IE 支持不好,可能有兼容问题。现代浏览器均支持良好。
Q3:能用 CSS Variables 做更复杂动画吗? A3:可以利用 CSS 变量配合 JS 动态调整布局,结合过渡动画效果,做出更生动的瀑布流。
七、总结
本文分享了用 CSS Grid + 少量 JS 实现响应式瀑布流布局的完整方案,重点在于:
- 用
grid-template-columns
和grid-auto-rows
定义基础网格 - JS 动态计算每个元素高度对应跨几行,实现灵活高度
grid-auto-flow: dense
保证紧凑无缝布局- 结合响应式设计,实现手机、平板、PC多端兼容
这套方案性能优、代码简洁、易维护,特别适合图片墙、电商商品页、内容卡片列表等常见场景。
你可以直接拿本文完整示例代码试一试,按需调整参数和样式,打造属于自己的漂亮瀑布流页面。