Vue虚拟滚动简单实现

80 阅读1分钟
<template>
  <div class="scroll-container" @scroll="onScroll">
    <div
      class="row"
      v-for="(item, index) in visibleItems"
      :key="startIndex + index"
      :style="rowStyle(index)"
    >
      {{ item }}
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';

import { throttle } from 'lodash-es';



// 数据源和初始化
const items = ref(Array.from({ length: 10000000 }, (_, i) => `Item ${i + 1}`));
const rowHeight = ref(50); // 每行高度
const visibleRowsCount = ref(0); // 可见行数
const startIndex = ref(0); // 当前起始索引

// 计算可见的内容
const visibleItems = computed(() =>
  items.value.slice(startIndex.value, startIndex.value + visibleRowsCount.value)
);

// 优化:限制渲染范围
const calculateVisibleRows = () => {
  const containerHeight = document.querySelector('.scroll-container')?.clientHeight || 0;
  visibleRowsCount.value = Math.ceil(containerHeight / rowHeight.value) + 2; // 增加两行缓冲
};


let animationFrame: number | null = null;

const onScroll = throttle(() => {

  if (animationFrame !== null) {
    cancelAnimationFrame(animationFrame); // 清理上一帧任务
  }
  animationFrame = requestAnimationFrame(() => {
    const container = document.querySelector('.scroll-container') as HTMLElement;
    const scrollTop = container.scrollTop;
    startIndex.value = Math.floor(scrollTop / rowHeight.value);
  });
}, 16); // 16ms 大约相当于每秒 60 帧

// 行样式:确保每一行位置准确
const rowStyle = (index: number) => ({
  height: `${rowHeight.value}px`,
  transform: `translateY(${(startIndex.value + index) * rowHeight.value}px)`, // 正确计算偏移量
  width: '100%',
  position: 'absolute' as 'absolute', // 使用绝对定位
  backgroundColor: (startIndex.value + index) % 2 === 0 ? 'black' : 'red',
  color: 'white',
  display: 'flex',
  alignItems: 'center',
  justifyContent: 'center'
});

// 初始化
onMounted(() => {
  calculateVisibleRows(); // 计算可见行数
  onScroll(); // 初始化滚动位置
});
</script>

<style>
.scroll-container {
  height: 800px; /* 固定容器高度 */
  overflow-y: auto; /* 开启垂直滚动 */
  position: relative;
  will-change: transform; /* 提示浏览器优化 */
}

.row {
  display: flex;
  align-items: center; /* 内容居中 */
  background-color: aquamarine; /* 默认背景色 */
}
</style>