Vue瀑布流长列表渲染卡顿,怎么优化?
最近在做图片瀑布流页面,用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的逻辑卡在布局重排上。有没有更好的实现方式或者需要调整的代码细节?
@load触发 Vue 的响应式更新,加上 Grid 布局每次图片加载完成都会重排,500张一起触发,浏览器直接崩溃。我司线上瀑布流也是从这坑里爬出来的,给你三个层级的解决方案,按优先级来:
先改最简单的:别在
@load里直接改 data,改成用ref存高度,再用requestAnimationFrame批量更新布局,这样能避免每张图都触发一次重排。代码大概是这样:但这个只是临时止血,真要干掉卡顿,必须上虚拟滚动。建议用
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上绑定mouseenter、click之类高频事件,这些也会拖垮滚动帧率。你现在的CSS Grid布局加v-for直接渲染500条,浏览器要做大量布局计算。再加上图片加载触发的onImageLoad频繁更新rowSpan,造成频繁重排重绘,性能扛不住很正常。
优化方向:
虚拟滚动必须上。只渲染当前可视区域+缓冲区域的图片,滚动时动态更新。推荐使用[Vue Virtual Scroller](https://github.com/Akryum/vue-virtual-scroller),开箱即用。
图片懒加载不要用v-lazy。原生的IntersectionObserver性能更好,比如这样:
动态计算rowSpan的问题。你需要等图片实际加载完成后再计算高度,不能直接在onImageLoad里改数据。建议在图片onload之后,用requestIdleCallback来处理布局更新。
CSS方面加点force硬件加速:
图片预加载和压缩也很关键。确保图片本身是webp格式,而且服务端做了响应式裁剪。
如果还是卡,可以考虑退而求其次用绝对定位模拟瀑布流,用js计算每个item的top/left。虽然麻烦,但比Grid布局在大数据量下更可控。
把这些都改完之后,我估计你500条数据也能顺畅滑动。我之前就是这么搞定的,卡顿问题基本消失。