虚拟滚动列表
在大数据量级渲染时常用到虚拟滚动,各大组件库也都支持,本文也将简单实现一个demo,剖析其原理。
demo中通过20个dom元素,实现了1000条数据的虚拟渲染。
原理
通过三个盒子搭配:
- 最外层盒子开启滚动,作为滚动列表视窗。
- 高度盒子渲染数据实际高度,但不渲染数据,实现滚动效果。
- 渲染盒子通过计算滚动距离,动态渲染应该展示的数据,并保持在视窗中。
源码
RFC
import React, { useState, useRef, useMemo } from 'react';
import './index.less';
export default function Index() {
// 初始配置
const expendCount = 4; // 多渲染数量
const screenHeight = 560; // 渲染屏幕高度
const renderItemHeight = 35; // 每条数据的固定高度
// 滚动容器Ref
const scrollBoxRef = useRef(null);
// 数据构造
const data = new Array(1000).fill(0).map((_, index) => `${index + 1}`);
// 开始,结束渲染索引
const [endIndex, setEndIndex] = useState(20);
const [startIndex, setStartIndex] = useState(0);
// 渲染列表
const renderList = useMemo(
() => data.slice(startIndex, endIndex),
[startIndex, endIndex],
);
// 滚动监听
const virtualBoxScroll = () => {
// 滑动距离
const scrollDistance = scrollBoxRef.current.scrollTop;
// 计算新索引
const startIndex = Math.floor(scrollDistance / renderItemHeight);
const endIndex =
startIndex + Math.ceil(screenHeight / renderItemHeight) + expendCount; // 多渲染5条
// 更新索引
setStartIndex(startIndex);
setEndIndex(endIndex);
};
return (
<div className="virtual-wrap">
{/* 外层盒子 屏幕高度 可滚动*/}
<div
className="virtual-scroll-wrap"
ref={scrollBoxRef}
style={{ height: `${screenHeight}px` }}
onScroll={virtualBoxScroll}
>
{/* 滚动盒子 渲染高度 空盒子 撑起高度*/}
<div
className="scroll-box"
style={{ height: `${data.length * renderItemHeight}px` }}
></div>
{/* 显示盒子 实际渲染条目 */}
<div
className="render-box"
style={{
transform: `translateY(${startIndex * renderItemHeight}px)`,
}}
>
{renderList?.map((item) => (
<div
className="scroll-item"
style={{ height: `${renderItemHeight}px` }}
key={item}
>
<span></span>
{item}
</div>
))}
</div>
</div>
</div>
);
}
Style
.virtual-wrap{
display: flex;
justify-content: center;
align-items: center;
background-color: black;
height: 100vh;
overflow: hidden;
box-sizing: border-box;
border: 1px solid pink;
.virtual-scroll-wrap{
width: 300px;
border: 4px solid skyblue;
overflow-y: auto;
position: relative;
background-color: azure;
.render-box{
position: absolute;
top: 0;
left: 0;
width: 100%;
}
.scroll-item{
display: flex;
align-items: center;
box-sizing: border-box;
padding:0 20px;
color: #2b2b2b;
border-bottom:1px solid #2b2b2b ;
background-color: azure;
box-shadow: 0 0 4px #ccc;
span{
display: inline-block;
margin-right: 15px;
border-radius: 50%;
height: 16px;
width: 16px;
background-color: pink;
}
}
}
}