Vue 3 不定高虚拟列表通过 ResizeObserver 动态测量+WeakMap 缓存高度+累积逼近法计算可视范围,配合占位元素实时更新滚动条,实现流畅渲染。

Vue 3 中实现不定高列表的虚拟化渲染,核心在于绕过“高度未知”带来的计算死锁,不依赖预设值硬算位置,而是用动态测量 + 缓存 + 响应式更新三者协同。固定高方案靠 scrollTop / itemHeight 直接算索引,而不定高必须先知道每项真实高度,才能定位可视区域——这正是难点所在。
用 ResizeObserver 主动监听真实高度
不要等滚动时再测量,而是在每个列表项挂载后,立即用 ResizeObserver 监听其内容尺寸变化。它比 getBoundingClientRect 更轻量、不触发重排,且能响应图片加载、字体渲染完成等异步布局变化。
- 每个
<ListItem>组件内部创建ResizeObserver实例,观测第一层内容容器(如<div class="content">) - 高度测得后,通过事件或上下文(如
provide/inject)通知父级虚拟列表更新缓存 - 首次进入视口前就完成测量,后续滚动可直接查缓存,避免重复 DOM 操作
用 WeakMap 缓存已知高度,避免重复开销
高度不是全局状态,而是与具体数据项绑定。用 WeakMap 存储 item → height 映射,既支持对象引用作为 key(适配复杂 item 结构),又不阻止垃圾回收。
Vue 3 组合式 API 与 最佳实践,涵盖代码组织、useXxx 模式、组合式函数设计、Store 集成、响应式优化及功能抽取。
- 初始化时:空
const itemHeightCache = new WeakMap() - 测量后:
itemHeightCache.set(item, domNode.clientHeight) - 计算可视范围前,先查缓存;未命中则返回预估高度(如 80px),保证滚动不中断
滚动时动态修正可视范围,不卡顿不白屏
可视起始索引 startIndex 不能靠公式一步到位,需用“累积高度逼近法”:从第 0 项开始累加已知高度,直到总和 ≥ scrollTop,即为起始项。结束索引同理,向后累加至超出视口底部。
立即学习“前端免费学习笔记(深入)”;
- 每次
scroll触发时,只遍历缓存中已有高度的项,跳过未测量项(用预估高度占位) - 设置
buffer(如 5 条),让可视区前后多渲染几项,避免快速滚动时因测量延迟导致空白 - 用
watchEffect自动追踪scrollTop和itemHeightCache变化,触发重新计算,无需手动调用
撑起滚动条:用总高度占位,而非真实 DOM
滚动条存在,靠的是一个绝对定位的 .placeholder 元素,其高度 = 所有项高度之和(缓存中有的用实测值,没有的用预估值)。这个高度必须实时可变,所以用 computed 包裹:
totalHeight: computed(() => props.items.reduce((sum, item) => sum + (itemHeightCache.get(item) ?? estimateSize), 0))- 该值驱动
.placeholder高度,滚动容器保持overflow: auto,用户滑动体验与原生一致 - 当某项高度被修正(如图片加载完成),
totalHeight自动更新,滚动条长度同步调整,无跳动

















