Vue项目实现虚拟滚动时出现无限向下滚动问题求助
Vue虚拟滚动无限向下滚动问题的解决方案
问题分析
你复刻React虚拟滚动实现时出现向下滚动无限到底的问题,核心原因是Vue响应式更新DOM时触发了额外的scroll事件,形成循环更新:当滚动触发start值更新,top/bottom占位div高度变化,浏览器重排后会再次触发scroll事件,导致start被反复计算直至最大值,最终直接滚到底部。向上滚动时因为start值只会减小,不会触发循环,所以表现正常。
修复步骤
1. 阻止滚动事件循环触发
添加标志位,在DOM更新期间跳过scroll事件处理,打破循环:
<template> <div ref="rootRef" :style="{ height: `${rowHeight * visibleRows + 1}px`, overflow: 'auto' }"> <div :style="{ height: `${topHeight}px` }"></div> <table> <tbody> <tr v-for="(row, rowIndex) in visibleData" :key="start + rowIndex" :style="{ height: `${rowHeight}px`, boxSizing: 'border-box' }"> <td v-for="(text, colIndex) in row" :key="start + rowIndex + colIndex">{{ text }}</td> </tr> </tbody> </table> <div :style="{ height: `${bottomHeight}px` }"></div> </div> </template> <script setup> import { ref, computed, onMounted, onUnmounted } from 'vue' const props = defineProps({ data: { type: Array, required: true }, rowHeight: { type: Number, required: true }, visibleRows: { type: Number, required: true } }) const rootRef = ref(null) const start = ref(0) let isUpdating = false // 阻止循环触发的标志位 // 计算顶部占位高度 const topHeight = computed(() => props.rowHeight * start.value) // 计算底部占位高度,确保不会出现负数 const bottomHeight = computed(() => { const remainingRows = props.data.length - (start.value + props.visibleRows + 1) return props.rowHeight * Math.max(0, remainingRows) }) // 计算可见数据 const visibleData = computed(() => { return props.data.slice(start.value, start.value + props.visibleRows + 1) }) function handleScroll(e) { if (isUpdating) return // 更新期间跳过处理 const scrollTop = e.target.scrollTop const newStart = Math.min( props.data.length - props.visibleRows - 1, // 最大边界,避免越界 Math.floor(scrollTop / props.rowHeight) ) if (newStart !== start.value) { isUpdating = true start.value = newStart // DOM更新完成后重置标志位 requestAnimationFrame(() => { isUpdating = false }) } } onMounted(() => { rootRef.current.addEventListener('scroll', handleScroll) }) onUnmounted(() => { rootRef.current.removeEventListener('scroll', handleScroll) }) </script>
2. 关键细节验证
- 固定行高:确保
<tr>的高度严格等于rowHeight,添加box-sizing: border-box避免padding/border影响实际高度,否则scrollTop计算会出现偏差。 - 边界计算:
start的最大取值必须是data.length - visibleRows - 1,保证slice的结束索引不超过数组长度,同时底部占位高度不会出现负数。 - 总高度固定:容器总高度始终等于
rowHeight * data.length,避免高度变化引发的滚动位置跳变。
总结
通过添加更新标志位阻止循环触发,同时严格控制行高和边界计算,就能解决无限向下滚动的问题。核心是避免Vue响应式DOM更新时反复触发scroll事件,打破循环链。
内容的提问来源于stack exchange,提问作者Алексей Абу
相关产品推荐
相关产品推荐

