虚拟列表技术
通过只渲染当前可见区域的数据来提高性能,而不是一次性渲染所有数据。这样可以显著减少 DOM 元素的数量,从而提高页面的加载速度和滚动流畅性。
核心思想
- 初始化容器和数据:创建固定高度的容器,准备数据源。
- 计算可视区域:获取容器高度,计算每个项的高度和可视区域的数据条数。
- 渲染初始可见区域:计算起始和结束索引,渲染初始数据。
- 监听滚动事件:绑定滚动事件,计算新的起始和结束索引,更新渲染数据。
- 调整样式:计算偏移量,处理实际列表跟随父容器一起移动的情况
实现效果
react框架展示虚拟列表实现步骤:
从上面的效果图看出,全程只创建了固定的8个div,无论你又多少条数据,就只产生八个div,滚动的时候,产生了滚动视觉效果,只是数据内容更新了。
- VirtualList组件
import {
useRef, useState, useMemo, useEffect } from "react";
import "./index.css";
function VirtualList({
listData, itemSize }) {
const listRef = useRef(null);
const [storeState, setStoreState] = useState({
screenHeight: 0, // 可视区域的高度
startOffset: 0, // 偏移量
start: 0, // 起始数据下标
end: 0, // 结束数据下标
});
const visibleData = useMemo(() => {
const {
start, end } = storeState;
return listData.slice(start, Math.min(listData.length, end));
}, [storeState]);
// 滚动的时候来更新起始数据
const scrollEvent = (e) => {
const scrollTop = e.target.scrollTop;
storeState.start = Math.floor(scrollTop / itemSize);
storeState.end = storeState.start + visibleCount;
storeState.startOffset = scrollTop - (scrollTop % itemSize);
setStoreState({
...storeState });
};
// 一直在更新的高度
const getTransform = useMemo(() => {
return `translate3d(0, ${
storeState.startOffset}px, 0)`;
}, [storeState]);
// 当前列表总高度
const listHeight = useMemo(() => {
return listData.length * itemSize;
}, [listData]);
// 能看到的数据
const visibleCount = useMemo(() => {
return storeState.screenHeight / itemSize;
}, [storeState]);
useEffect(() => {
storeState.screenHeight = listRef.current.clientHeight;
storeState.end = storeState.start + visibleCount;
setStoreState({
...storeState });
}, [visibleCount, setStoreState]);
return (
<>
<div
ref={
listRef}
className="infinite-list-container"
onScroll={
scrollEvent}
>
<div
className="infinite-list-phantom"
style={
{
height: listHeight + "px" }}
></div>
<div className="infinite-list" style={
{
transform: getTransform }}>
{
visibleData.map((item, index, array) => {
return (
<div
className="infinite-list-item"
key={
index}
style={
{
height: itemSize + "px", lineHeight: itemSize + "px" }}
>
{
item.name}
</div>
);
})}
</div>
</div>
</>
);
}
VirtualList.defaultProps = {
itemSize: 50, listData: [] };
export default VirtualList;
- 组件的使用
import VirtualList from "./virtualList";
function Vlist() {
const listData = Array.from({
length: 1000 }, (_, index) => ({
id: index,
name: `item ${
index}`,
value: `value ${
index}`,
}));
return (
<div className="container-box">
<VirtualList listData={
listData} />
</div>
);
}
export default Vlist;
- index.css文件
.container-box{
height: 400px;
width: 100%;
background-color: aliceblue;
border: #000 1px solid;
}
.infinite-list-container {
height: 100%;
overflow: auto;
position: relative;
-webkit-overflow-scrolling: touch; /* 启用触摸滚动 */
}
.infinite-list-phantom{
position: absolute;
left: 0;
top: 0;
right: 0;
z-index: -1; /* 置于背景层 **/
}
.infinite-list{
position: absolute;
left: 0;
top: 0;
right: 0;
text-align: center;
}
.infinite-list-item {
border-bottom: 1px solid #000;
box-sizing: border-box;
}