大量dom导致浏览器渲染了全部dom导致页面严重卡顿、交互延迟、CPU 占用飙升、内存急剧增大滚动频繁掉帧等问题细想一下用户并不会同时看到所有数据所以我们只需借助虚拟滚动只渲染用户可视区域内的数据减少大量 DOM 和渲染压力提升页面流畅度及用户体感。一、具体实现外层容器固定高度 overflow-y: auto这是用户视图占位 div高度设为总条数 × 行高它不显示任何内容只负责撑出滚动条真实渲染区position: absolutetransform: translateY()根据滚动位置动态移动用户滚动时监听scrollTop算出当前视图下应该展示哪几条数据然后更新真实渲染区的数据更新translateY模拟跟着滚动条走这样用户感觉自己在滚一个长列表实际上 DOM 节点始终只有十几个。如图二、核心实现// 1. 起始索引根据滚动距离算出第一个可见项 startIndex Math.floor(scrollTop / itemHeight) // 2. 结束索引起始 可视区能容纳的行数 endIndex startIndex Math.ceil(containerHeight / itemHeight) // 3. 偏移量真实渲染区相对于顶部的位置 offset startIndex * itemHeight例如scrollTop 500itemHeight 50containerHeight 500startIndex 500 / 50 10从第 10 条开始渲染endIndex 10 10 20渲染到第 20 条offset 10 × 50 500真实渲染区向下移动 500px三、完整代码vue3TStemplate div refcontainerRef classvirtual-list :style{ height: ${containerHeight}px } scrollhandleScroll !-- 占位撑出滚动条 -- div classvirtual-list__phantom :style{ height: ${totalHeight}px } / !-- 真实渲染区transform 定位 -- div classvirtual-list__content :style{ transform: translateY(${offset}px) } div v-foritem in visibleData :keyitem.id classvirtual-list__item :style{ height: ${itemHeight}px } slot :itemitem{{ item.name }}/slot /div /div /div /template script setup langts import { ref, computed } from vue; interface Item { id: number | string; [key: string]: any; } const props withDefaults( defineProps{ data: Item[]; itemHeight?: number; containerHeight?: number; buffer?: number; }(), { itemHeight: 50, containerHeight: 500, buffer: 5, } ); const containerRef refHTMLElement | null(null); const scrollTop ref(0); // 总高度 const totalHeight computed(() props.data.length * props.itemHeight); // 可视区能容纳的行数 const visibleCount computed(() Math.ceil(props.containerHeight / props.itemHeight) ); // 起始索引减 buffer 避免向上滚动时白屏 const startIndex computed(() Math.max(0, Math.floor(scrollTop.value / props.itemHeight) - props.buffer) ); // 结束索引(加 2 倍 buffer 让上下都有缓冲) const endIndex computed(() Math.min( props.data.length, startIndex.value visibleCount.value props.buffer * 2 ) ); // 需要渲染的数据切片 const visibleData computed(() props.data.slice(startIndex.value, endIndex.value) ); // 偏移量关键渲染区模拟在正确位置 const offset computed(() startIndex.value * props.itemHeight); function handleScroll(e: Event) { scrollTop.value (e.target as HTMLElement).scrollTop; } /script style scoped .virtual-list { position: relative; overflow-y: auto; border: 1px solid #ddd; } .virtual-list__phantom { position: absolute; top: 0; left: 0; right: 0; z-index: -1; } .virtual-list__content { position: absolute; top: 0; left: 0; right: 0; will-change: transform; } .virtual-list__item { display: flex; align-items: center; padding: 0 16px; border-bottom: 1px solid #eee; box-sizing: border-box; } /style使用方式VirtualList :datalist :item-height50 :container-height500 template #default{ item } span#{{ item.id }}/span span stylemargin-left: 20px{{ item.name }}/span /template /VirtualList