认识
2024年01月27日
一、认识
rc-virtual-list 是一款高性能的虚拟列表组件, 支持固定高度、动态高度。
二、语法
2.1 静态高度
import VirtualList from 'rc-virtual-list';
<VirtualList data={[0, 1, 2]} height={200} itemHeight={30} itemKey="id">
{index => <div>{index}</div>}
</VirtualList>;
2.2 动态高度
import VirtualList from 'rc-virtual-list';
import { forwardRef, useRef } from 'react';
const data = new Array(100).fill(0).map((_value, index) => ({
id: index,
height: 30 + (index % 2 ? 70 : 0)
}));
const Item = (props, ref) => {
const { id, index: _index, height, style } = props;
return (
<div
ref={ref}
style={{
...style,
height,
padding: '0 16px',
lineHeight: '30px',
boxSizing: 'border-box',
border: '1px solid gray'
}}
>
{id}
</div>
);
};
const ItemWrapper = forwardRef(Item);
function App() {
const listRef = useRef(null);
const onScroll = e => {
console.log('scroll:', e.currentTarget.scrollTop);
};
const onScrollTopTo = value => {
listRef.current.scrollTo({
index: value,
align: 'top'
});
};
const onScrollBottomTo = value => {
listRef.current.scrollTo({
index: value,
align: 'bottom'
});
};
return (
<div>
<h2>My Virtual List</h2>
<h2>
<button onClick={() => onScrollTopTo(50)}>scroll to top 50</button>
<button onClick={() => onScrollBottomTo(50)}>
scroll to bottom 50
</button>
</h2>
<VirtualList
data={data}
height={500}
itemKey="id"
ref={listRef}
itemHeight={30}
onScroll={onScroll}
style={{
border: '1px solid red',
boxSizing: 'border-box'
}}
>
{(item, index, others) => (
<ItemWrapper {...item} index={index} {...others} />
)}
</VirtualList>
</div>
);
}
export default App;
三、问题
3.1 如何解决快速滚动时白屏闪烁的问题?
由于每次只渲染有限数量条目。当快速滚屏时,也会遇到滚动跟不上的情况, 造成了白屏闪烁的问题。rc-virtual-list
进行了如下的探索:
-
劫持
onScroll
事件, 计算对应数据后, 在通过设置scrollTop
滚动到相应位置。遗憾的是:scroll
事件是先发生滚动, 后触发UI Event
。 所以, 无法在onScroll
事件中调用e.preventDefault
阻止滚动。 -
劫持
onScroll
事件的前置事件onWheel
,调用e.preventDefault()
阻止滚动, 在浏览器的一帧里面,根据滚动值更新scrollTop
即可。
function onWheel({ deltaY }: WheelEvent) {
event.preventDefault();
cancelAnimationFrame(nextFrameRef.current);
offsetRef.current += deltaY;
nextFrameRef.current = requestAnimationFrame(() => {
listRef.current.scrollTop += offsetRef.current;
offsetRef.current = 0;
});
}
完成这些操作后,我们就可以做到滚动时候的无白屏效果而不需要配置缓冲数据。
3.2 如何解决直接拖拽滚动条造成的白屏场景?
rc-virtual-list
设置 overflow: hidden
直接隐藏滚动条,并额外实现了一个假的滚动条来代替原生的。利用这个滚动条,我们就可以做到每次拖拽会先经过我们的虚拟滚动逻辑,再进行滚动操作。每次计算时,都通过滚动条所在位置的百分比还原回 scrollTop
的值。由于滚动条位置不依赖实际高度,这使得我们一并解决了上文提到的 Item
第一次渲染高度变化导致的滚动条与鼠标不同步的问题。