一步步教你用 CSS Grid 实现灵活又高效的瀑布流布局,适配所有屏幕!

摘要

随着网页设计越来越复杂,尤其是图片墙、商品展示、内容卡片这类页面,瀑布流布局(也叫 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 能正确渲染布局
  • 绑定到 loadresize 事件,动态响应不同屏幕尺寸和内容变更

实际场景举例

图片墙

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-columnsgrid-auto-rows 定义基础网格
  • JS 动态计算每个元素高度对应跨几行,实现灵活高度
  • grid-auto-flow: dense 保证紧凑无缝布局
  • 结合响应式设计,实现手机、平板、PC多端兼容

这套方案性能优、代码简洁、易维护,特别适合图片墙、电商商品页、内容卡片列表等常见场景。

你可以直接拿本文完整示例代码试一试,按需调整参数和样式,打造属于自己的漂亮瀑布流页面。

相关推荐
cos2 小时前
FE Bits 前端周周谈 Vol.2|V8 提速 JSON.stringify 2x,Vite 周下载首超 Webpack
前端·javascript·css
一枚前端小能手10 小时前
💎 Less/Sass写出优雅代码的4个高级技巧
css·less
XboxYan12 小时前
借助CSS实现一个花里胡哨的点赞粒子动效
前端·css
阿珊和她的猫13 小时前
rem:CSS中的相对长度单位
前端·css
超级小忍13 小时前
CSS 选择器进阶:用更聪明的方式定位元素
前端·css
我有一粒花生米13 小时前
css 瀑布流布局
前端·javascript·css
我想说一句16 小时前
有了TailwindCSS,CSS不再需要你写了!
前端·css·前端框架
薛定谔的算法16 小时前
Tailwind CSS:原子化CSS的现代革命
前端·css
今禾16 小时前
TailwindCSS 与 -webkit-line-clamp 深度解析:现代前端开发的样式革命
前端·css·webkit