虚拟列表是一种前端性能优化方案,其核心思想是通过只渲染可视区域或者加上缓冲区部分区域的 DOM 元素,其他部分通过空占位符维持滚动条高度,以达到性能优化的目的
简化版:
结构:
- 外层容器,需要拿到这个元素的srollTop,即隐藏容器的顶部,距离外层容器顶部的距离
- 隐藏容器:需要用隐藏容器设置为列表的实际高度(item_height * count) 来撑开外层容器,以达到出现滚动条的目的
- 实际渲染列表容器: 拿到计算好的 startIndex 以及 endIndex,仅渲染这部分的列表,再通过计算出偏移量(即列表容器本应该在隐藏容器的顶部,需要偏移到可视区域) 设置transform: transtateY(offset) 来将容器移动到可视区域
import React, { useState, useRef, useCallback, useMemo } from 'react';
/**
* VirtualList 组件
* @param {Array} items - 列表数据源
* @param {number} itemHeight - 每个列表项的固定高度
* @param {number} containerHeight - 容器的可视高度
*/
function VirtualList({ items, itemHeight = 50, containerHeight = 600 }) {
const listRef = useRef(null);
const [scrollTop, setScrollTop] = useState(0);
// 处理滚动事件,记录当前的滚动距离
const handleScroll = useCallback(() => {
if (listRef.current) {
setScrollTop(listRef.current.scrollTop);
}
}, []);
// --- 核心计算逻辑 ---
// 1. 计算可视区域内可以显示多少个元素,额外多加 2 个作为缓冲区(Buffer)防止白屏
const visibleCount = useMemo(() => {
return Math.ceil(containerHeight / itemHeight) + 2;
}, [containerHeight, itemHeight]);
// 2. 根据滚动距离,计算当前应该从第几个元素开始渲染
const startIndex = Math.floor(scrollTop / itemHeight);
// 3. 计算结束索引
const endIndex = Math.min(items.length, startIndex + visibleCount);
// 4. 获取当前需要渲染的数据切片
const visibleItems = items.slice(startIndex, endIndex);
// 5. 计算偏移量:让渲染区域始终跟随滚动条向下移动
const offsetTop = startIndex * itemHeight;
// --- 样式定义 ---
const containerStyle = {
height: `${containerHeight}px`,
overflowY: 'auto',
position: 'relative',
border: '1px solid #ccc'
};
const phantomStyle = {
height: `${items.length * itemHeight}px`, // 总高度,撑起滚动条
position: 'absolute',
left: 0,
top: 0,
right: 0,
zIndex: -1
};
const listContentStyle = {
transform: `translateY(${offsetTop}px)`, // 偏移量,将内容移动到可视区
width: '100%'
};
return (
<div ref={listRef} onScroll={handleScroll} style={containerStyle}>
{/* 幻影层:负责撑开滚动条 */}
<div style={phantomStyle} />
{/* 真实渲染层:只渲染可见的部分 */}
<div style={listContentStyle}>
{visibleItems.map((item, index) => (
<div
key={startIndex + index}
style={{
height: `${itemHeight}px`,
lineHeight: `${itemHeight}px`,
borderBottom: '1px solid #eee',
boxSizing: 'border-box'
}}
>
{item}
</div>
))}
</div>
</div>
);
}
export default VirtualList;不定高方案
虚拟列表本来采用的是静态高度+直接计算偏移的方式,而在大模型输出文字流的场景当中,本身文本流是不定高的。此时就要采用 预估高度 + 动态偏移 的原理。
- 给每个 Item 一个预估高度(Estimated Height)。
- 渲染后通过
ResizeObserver或ref获取真实高度并缓存。 - 动态更新后续所有元素的
top偏移量和滚动条的总高度。
大数锚点方案
大数锚点 是一个虚拟列表解决视口跳动的方案,具体参考元点引擎的那个项目是怎么写的。