Vue瀑布流长列表渲染卡顿,怎么优化?

A. 付楠 阅读 88

最近在做图片瀑布流页面,用CSS Grid布局配合v-for渲染500条数据,滚动特别卡顿。试过把图片懒加载改成v-lazy,但滑动到中间位置就直接卡死不动了。

代码结构大概是这样写的:


<template>
  <div class="grid" :style="{ '--cols': cols }">
    <div 
      v-for="(item, index) in items" 
      :key="index" 
      class="grid-item"
      :style="{ '--rowSpan': item.rowSpan }"
    >
      <img :src="item.url" @load="onImageLoad(index)">
    </div>
  </div>
</template>

<style>
.grid { display: grid; gap: 10px; grid-template-columns: repeat(var(--cols), 1fr); }
.grid-item { grid-row-end: span var(--rowSpan); }
</style>

试过用计算属性分段渲染,但计算图片实际高度时总出错,动态计算rowSpan的逻辑卡在布局重排上。有没有更好的实现方式或者需要调整的代码细节?

我来解答 赞 9 收藏
二维码
手机扫码查看
2 条解答
FSD-红霞
卡顿的根本原因不是500条数据本身,而是你每张图都直接绑定 @load 触发 Vue 的响应式更新,加上 Grid 布局每次图片加载完成都会重排,500张一起触发,浏览器直接崩溃。

我司线上瀑布流也是从这坑里爬出来的,给你三个层级的解决方案,按优先级来:

先改最简单的:别在 @load 里直接改 data,改成用 ref 存高度,再用 requestAnimationFrame 批量更新布局,这样能避免每张图都触发一次重排。代码大概是这样:

export default {
data() {
return {
items: [],
imgHeights: ref({})
}
},
methods: {
onImageLoad(index, naturalHeight) {
this.imgHeights.value[index] = naturalHeight
requestAnimationFrame(() => {
this.recalcRowSpan()
})
},
recalcRowSpan() {
// 这里只在raf里做一次计算,别每张图都算
const rowSpans = calculateRowSpansBasedOnHeights(this.imgHeights.value)
this.items = this.items.map((item, i) => ({ ...item, rowSpan: rowSpans[i] }))
}
}
}


但这个只是临时止血,真要干掉卡顿,必须上虚拟滚动。建议用 vue-virtual-scroller 这个库,它支持 dynamic-size 模式,配合瀑布流插件(比如 vue-virtual-scroller + vue-masonry)能支持动态高度。

不过如果你不想引入第三方库,可以自己实现个极简虚拟瀑布流:把数据分块,每块渲染20条,滚动时用 IntersectionObserver 监听每个 grid-item 的可见性,只渲染视口+缓冲区里的元素,其他用占位高度的 div 替代。关键点是:永远只渲染可见区域+上下各10条,500条里实际 DOM 节点永远不超过 30~40 个。

最后提一句,CSS Grid 虽然写起来爽,但动态高度瀑布流真不如用 JS 模拟两列(或三列)并列渲染,比如 Masonry 布局本质就是几个垂直列拼出来的,比 Grid 更可控,性能也好不少。

如果还卡,检查下有没有在 img 上绑定 mouseenterclick 之类高频事件,这些也会拖垮滚动帧率。
点赞 2
2026-02-24 22:15
南宫世玉
你这个问题我遇到过,核心问题在于两点:一是瀑布流布局在大量DOM节点下的渲染性能瓶颈,二是图片懒加载和动态高度计算导致的主线程阻塞。

你现在的CSS Grid布局加v-for直接渲染500条,浏览器要做大量布局计算。再加上图片加载触发的onImageLoad频繁更新rowSpan,造成频繁重排重绘,性能扛不住很正常。

优化方向:

虚拟滚动必须上。只渲染当前可视区域+缓冲区域的图片,滚动时动态更新。推荐使用[Vue Virtual Scroller](https://github.com/Akryum/vue-virtual-scroller),开箱即用。

图片懒加载不要用v-lazy。原生的IntersectionObserver性能更好,比如这样:
const img = document.querySelector('img')
const io = new IntersectionObserver(entries => {
if (entries[0].isIntersecting) {
img.src = img.dataset.src
io.disconnect()
}
})
io.observe(img)


动态计算rowSpan的问题。你需要等图片实际加载完成后再计算高度,不能直接在onImageLoad里改数据。建议在图片onload之后,用requestIdleCallback来处理布局更新。

CSS方面加点force硬件加速:
.grid {
will-change: transform;
contain: layout;
}


图片预加载和压缩也很关键。确保图片本身是webp格式,而且服务端做了响应式裁剪。

如果还是卡,可以考虑退而求其次用绝对定位模拟瀑布流,用js计算每个item的top/left。虽然麻烦,但比Grid布局在大数据量下更可控。

把这些都改完之后,我估计你500条数据也能顺畅滑动。我之前就是这么搞定的,卡顿问题基本消失。
点赞 14
2026-02-05 09:02