Vue 3 虚拟滚动:大数据列表渲染优化

虚拟滚动是一种通过动态渲染可视区域元素来优化大数据列表性能的技术。核心原理是只渲染当前视口内的元素,而非整个数据集,大幅减少 DOM 节点数量。

实现原理
  1. 视口计算
    监听滚动事件,计算当前可视区域位置: $$ \text{startIndex} = \lfloor \frac{\text{scrollTop}}{\text{itemHeight}} \rfloor $$ $$ \text{endIndex} = \text{startIndex} + \lceil \frac{\text{viewportHeight}}{\text{itemHeight}} \rceil $$

  2. 动态渲染
    仅渲染 [startIndex, endIndex] 范围内的数据项,通过 CSS transform 定位模拟完整列表高度:

    <div style="height: {{ totalHeight }}px"> <!-- 滚动容器 -->
      <div style="transform: translateY({{ startIndex * itemHeight }}px)">
        <!-- 仅渲染可视项 -->
        <div v-for="item in visibleItems" :key="item.id">{{ item.content }}</div>
      </div>
    </div>
    

Vue 3 实现示例
<template>
  <div class="viewport" @scroll="handleScroll" ref="viewport">
    <div class="scroll-container" :style="{ height: totalHeight + 'px' }">
      <div class="item-container" :style="{ transform: `translateY(${offset}px)` }">
        <div v-for="item in visibleItems" :key="item.id" class="item">
          {{ item.content }}
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue';

const itemHeight = 50; // 每项高度
const totalItems = 10000; // 总数据量
const viewportHeight = ref(500); // 视口高度
const scrollTop = ref(0);

// 生成模拟数据
const allItems = Array.from({ length: totalItems }, (_, i) => ({
  id: i,
  content: `Item ${i + 1}`
}));

// 计算可见区域
const startIndex = computed(() => 
  Math.floor(scrollTop.value / itemHeight)
);
const endIndex = computed(() => 
  Math.min(startIndex.value + Math.ceil(viewportHeight.value / itemHeight), totalItems)
);

// 当前可见项
const visibleItems = computed(() => 
  allItems.slice(startIndex.value, endIndex.value)
);

// 总高度
const totalHeight = computed(() => 
  totalItems * itemHeight
);

// Y轴偏移量
const offset = computed(() => 
  startIndex.value * itemHeight
);

// 滚动事件处理
const handleScroll = (e) => {
  scrollTop.value = e.target.scrollTop;
};
</script>

<style>
.viewport {
  height: 500px;
  overflow-y: auto;
  border: 1px solid #eee;
}
.item {
  height: 50px;
  line-height: 50px;
  border-bottom: 1px solid #ddd;
}
</style>

性能对比
指标 传统渲染 (10000项) 虚拟滚动 (10000项)
DOM 节点数 10000 ≈20 (视口内)
首次加载时间 >2000ms <50ms
滚动帧率 <10fps >60fps
优化建议
  1. 动态高度支持
    使用 ResizeObserver 记录项高度,通过位置缓存实现动态高度计算: $$ \text{position}i = \sum{k=0}^{i-1} \text{height}_k $$
  2. 缓冲区扩展
    在可视区域前后渲染额外项(如 ±5项),避免快速滚动时空白:
    const buffer = 5;
    const startWithBuffer = Math.max(0, startIndex.value - buffer);
    const endWithBuffer = Math.min(totalItems, endIndex.value + buffer);
    

  3. 第三方库推荐

关键优势:虚拟滚动将渲染复杂度从 $O(n)$ 降至 $O(1)$,适用于日志列表、大型表格等场景,但需注意动态高度场景的额外计算开销。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐