跳到主要内容

认识

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 进行了如下的探索:

  1. 劫持 onScroll 事件, 计算对应数据后, 在通过设置 scrollTop 滚动到相应位置。遗憾的是: scroll 事件是先发生滚动, 后触发 UI Event。 所以, 无法在 onScroll 事件中调用 e.preventDefault 阻止滚动。

  2. 劫持 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 第一次渲染高度变化导致的滚动条与鼠标不同步的问题。

参考资料


Ant Design 4.0 的一些杂事儿 - VirtualList 篇