You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,提问作者Алексей Абу

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 18:37:36