携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情
虚拟列表定义
虚拟列表其实就是当我们前端页面渲染大量数据的时候,如果一次性渲染出来,一是非常耗费浏览器性能,造成页面卡顿,甚至崩溃,二是用户和体验非常不好,虚拟列表就是在我们的可视区域进行数据的渲染,当滚动到后面数据的时候再进行加载下面的数据,达到分批次渲染的目的
虚拟列表的原理和实现
原理图
实现的步骤
- 外层容器高度固定(550),并且设置(overflowY: ‘auto’)
- 计算内层容器放下全部数据的高度(total * 55)
- 计算在这个固定容器高度下可视区可以装多少条数据(limit = 550 / 55)
- 滚动的时候,通过参数scrollTop,可以计算可视区展示的第一条数据是第几条(startIndex = scrollTop / 55 )和最后一条数据是第几条(endIndex = limit + startIndex)
- 优化,加一个缓冲条数bufferSize,防止鼠标滚动过快的时候出现空白区域
import { select } from "@querycap-ui/core";
const total = 100000;
const rowHeight = 55;
// 用于缓冲(防止滚动过快,出现白屏)
const bufferSize = 20;
const limit = 10;
let originStartIdx = 0;
const getdata = () => {
const data = [];
for (let i = 0; i < total; i++) {
data.push({
title: `标题${i}`,
age: `年纪${i}`,
address: `地址${i}`,
})
}
return data;
}
const data = getdata();
const Home = () => {
const [current, setCurrent] = useState<undefined | number>();
const [startIndex, setStart] = useState(Math.max(originStartIdx - bufferSize, 0));
const [endIndex, setEnd] = useState(Math.min(originStartIdx + limit + bufferSize, total - 1));
const ref = useRef<HTMLDivElement>(null);
const onScroll = (e: any) => {
if (e.target === ref.current) {
const { scrollTop } = e.target;
// scrollTop 获取到的是被滚动元素在垂直位置滚动的距离。
const currIndex = Math.floor(scrollTop / rowHeight);
if (originStartIdx !== currIndex) {
originStartIdx = currIndex;
setStart(Math.max(currIndex - bufferSize, 0))
setEnd(Math.min(currIndex + limit + bufferSize, total - 1))
}
}
}
const edite = (data: any, index: number) => {
setCurrent(index);
console.log('编辑了这一条数据---->',data)
}
const rowRenderer = (obj: any) => {
const { index, style } = obj;
return (
<div
key={index}
style={style}
css={
select().backgroundColor(index === current ? '#dcebf8' : '').display('flex')
.with(select(':hover').backgroundColor(index === current ? '#dcebf8' : '#fafafa'))
.with(select('>*').flex(1).padding('0px 16px'))
}
>
<span>{data[index].title}</span>
<span>{data[index].age}</span>
<span>{data[index].address}</span>
<a onClick={() => edite(data[index], index)}>编辑</a>
</div>
)
}
// 可视区域的数据
const pushData = () => {
const content = [];
for (let i = startIndex; i <= endIndex; ++i) {
content.push(
rowRenderer({
index: i,
style: {
height: `${rowHeight}px`,
lineHeight: `${rowHeight}px`,
left: 0,
right: 0,
position: "absolute",
top: i * rowHeight,
borderBottom: "1px solid #f0f0f0",
width: "100%",
cursor: 'pointer',
}
})
);
}
return content;
}
return (
<div
style={{
margin: '0 auto',
height: '550px',
width: '100%',
overflowY: 'auto',
msOverflowX: 'hidden',
border:'1px solid red',
}}
onScroll={onScroll}
ref={ref}
>
<div style={{ height: `${total * 55}px`, position: 'relative' }}>
{pushData()}
</div>
</div>
);
};
export default Home;