// src/hooks/useVirtualScroll.js
import { ref, onMounted, onUnmounted } from 'vue';
export function useVirtualScroll(itemHeight = 50, buffer = 5) {
const containerRef = ref(null);
const visibleData = ref([]);
const totalHeight = ref(0);
const startIndex = ref(0);
const endIndex = ref(0);
const updateVisibleItems = (scrollTop) => {
const containerHeight = containerRef.value?.clientHeight || 0;
startIndex.value = Math.max(0, Math.floor(scrollTop / itemHeight) - buffer);
endIndex.value = Math.min(
data.value.length,
startIndex.value + Math.ceil(containerHeight / itemHeight) + buffer * 2
);
visibleData.value = data.value.slice(startIndex.value, endIndex.value);
totalHeight.value = data.value.length * itemHeight;
};
const handleScroll = () => {
if (!containerRef.value) return;
const scrollTop = containerRef.value.scrollTop;
updateVisibleItems(scrollTop);
};
onMounted(() => {
containerRef.value?.addEventListener('scroll', handleScroll);
updateVisibleItems(0);
});
onUnmounted(() => {
containerRef.value?.removeEventListener('scroll', handleScroll);
});
return { containerRef, visibleData, totalHeight, startIndex };
}
这段代码实现了一个 虚拟滚动(Virtual Scroll)的 Vue3 Composition Hook,用于优化大数据量列表的渲染性能。以下是逐部分解析:
1. 核心功能
- 解决的问题:当渲染成千上万条数据时,直接渲染所有 DOM 节点会导致性能卡顿。
- 实现思路:
只渲染当前可视区域内的数据项,用空白占位符撑起滚动条高度,模拟完整列表。
2. 参数说明
export function useVirtualScroll(itemHeight = 50, buffer = 5) {
itemHeight
:每个列表项的固定高度(单位:px),默认 50px。buffer
:可视区域外的缓冲区项数(减少滚动时的空白闪烁),默认 5。
3. 关键变量
变量名 | 类型 | 作用 |
---|---|---|
containerRef | Ref<HTMLElement> | 绑定滚动容器的 DOM 元素 |
visibleData | Ref<Array> | 当前需要渲染的可见数据项(动态更新) |
totalHeight | Ref<number> | 所有数据项的总高度(用于撑开滚动条) |
startIndex | Ref<number> | 可视区域起始索引(动态计算) |
endIndex | Ref<number> | 可视区域结束索引(动态计算) |
4. 核心方法解析
**(1) updateVisibleItems(scrollTop)
**
const updateVisibleItems = (scrollTop) => {
// 1. 获取容器高度
const containerHeight = containerRef.value?.clientHeight || 0;
// 2. 计算可见项的起始/结束索引(考虑缓冲区)
startIndex.value = Math.max(0, Math.floor(scrollTop / itemHeight) - buffer);
endIndex.value = Math.min(
data.value.length,
startIndex.value + Math.ceil(containerHeight / itemHeight) + buffer * 2
);
// 3. 更新当前需要渲染的数据
visibleData.value = data.value.slice(startIndex.value, endIndex.value);
// 4. 计算总高度(用于占位)
totalHeight.value = data.value.length * itemHeight;
};
- 计算逻辑:
- 根据滚动位置
scrollTop
和容器高度,动态计算哪些数据项应该被渲染。 buffer
的作用是提前加载可视区域外的部分数据,避免滚动时出现空白。
- 根据滚动位置
**(2) handleScroll()
**
const handleScroll = () => {
if (!containerRef.value) return;
const scrollTop = containerRef.value.scrollTop;
updateVisibleItems(scrollTop); // 滚动时更新可见数据
};
- 监听容器的
scroll
事件,实时更新可见数据。
5. 生命周期管理
onMounted(() => {
containerRef.value?.addEventListener('scroll', handleScroll);
updateVisibleItems(0); // 初始化时渲染首屏数据
});
onUnmounted(() => {
containerRef.value?.removeEventListener('scroll', handleScroll);
});
- 挂载时:绑定滚动事件,初始化渲染。
- 卸载时:移除事件监听,避免内存泄漏。
6. 返回值
return { containerRef, visibleData, totalHeight, startIndex };
- 暴露给组件的变量:
containerRef
:绑定到滚动容器的ref
。visibleData
:当前需要渲染的数据子集。totalHeight
:所有数据的总高度(用于占位 div 的高度)。startIndex
:可视区域的起始索引(可用于调试)。
7. 在组件中的使用示例
<template>
<div ref="containerRef" class="scroll-container">
<!-- 占位元素(撑开滚动条) -->
<div :style="{ height: `${totalHeight}px` }"></div>
<!-- 实际渲染的可见数据 -->
<div
class="visible-items"
:style="{ transform: `translateY(${startIndex * itemHeight}px)` }"
>
<div
v-for="item in visibleData"
:key="item.id"
class="item"
:style="{ height: `${itemHeight}px` }"
>
{{ item.content }}
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useVirtualScroll } from '@/hooks/useVirtualScroll';
// 模拟大数据
const data = ref(Array.from({ length: 10000 }, (_, i) => ({
id: i,
content: `Item ${i + 1}`
})));
const itemHeight = 50; // 每项高度
const { containerRef, visibleData, totalHeight, startIndex } =
useVirtualScroll(itemHeight);
</script>
<style>
.scroll-container {
height: 500px;
overflow-y: auto;
position: relative;
}
.visible-items {
position: absolute;
top: 0;
left: 0;
width: 100%;
}
.item {
border-bottom: 1px solid #eee;
}
</style>
8. 性能优化点
- 固定高度:要求所有列表项高度一致(动态高度需复杂计算)。
- 缓冲区(buffer):减少滚动时的空白闪烁。
- 事件节流:实际项目中可对
handleScroll
添加节流(如lodash.throttle
)。
总结
- 输入:
itemHeight
(项高度)和buffer
(缓冲区大小)。 - 输出:动态计算的
visibleData
和容器总高度totalHeight
。 - 核心思想:
用极少的 DOM 节点(仅可见区域 + 缓冲区)模拟完整长列表,大幅提升性能。
适用于任何需要渲染大数据量的 Vue3 项目(如表格、聊天记录、商品列表)。