React实现虚拟列表
先看效果图:

虚拟列表具体实现思路:
1. 通过 useRef 获取元素,缓存变量。
2. useEffect 初始化计算容器的高度。截取初始化列表长度。这里需要 div 占位,撑起滚动条。
3. 通过监听滚动容器的 onScroll 事件,根据 scrollTop 来计算渲染区域向上偏移量, 这里需要注意的是,当用户向下滑动的时候,为了渲染区域,能在可视区域内,可视区域要向上 滚动;当用户向上滑动的时候,可视区域要向下滚动。
4. 通过重新计算 end 和 start 来重新渲染列表。
function ResponsiveDemo() {
const [dataList, setDataList] = React.useState<any>([]); //保存数据源
const [position, setPosition] = React.useState([0, 0]); //缓冲区+视图区索引
const scroll = React.useRef<any>(null); //获取scroll元素
const box = React.useRef<any>(null); //获取box容器高度
let context = React.useRef<any>(null); //用于移动视图区域,形成滑动效果
const scrollInfo = React.useRef({
height: 500,
bufferCount: 18, //缓冲区个数
itemHeight: 60, //每一个item的高度
renderCount: 0, //渲染区个数
});
React.useEffect(() => {
const height = box?.current?.offsetHeight;
const { itemHeight, bufferCount } = scrollInfo.current;
const renderCount = Math.ceil(height / itemHeight) + bufferCount;
scrollInfo.current = { renderCount, height, bufferCount, itemHeight };
const dataNewList = new Array(1000).fill(1).map((item, index) => index + 1);
setDataList(dataNewList);
setPosition([0, renderCount]);
}, []);
const handleScroll = () => {
const { scrollTop } = scroll.current;
const { itemHeight, renderCount } = scrollInfo.current;
const currentOffset = scrollTop - (scrollTop % itemHeight);
context.current.style.transform = `translate3d(0, ${currentOffset}px, 0)`; /* 偏移,造成下滑效果 */
//更新start和end
const start = Math.floor(scrollTop / itemHeight);
const end = Math.floor(scrollTop / itemHeight + renderCount + 1);
if (end ! == position[1] || start ! == position[0]) {
/* 如果render内容发生改变,那么截取 */
setPosition([start, end]);
}
};
const { itemHeight, height } = scrollInfo.current;
const [start, end] = position;
const renderList = dataList.slice(start, end); //渲染区间
return (
<div className={styles.list_box} ref={box}>
<div
className={styles.scroll_box}
style={{ height: height + 'px' }}
onScroll={handleScroll}
ref={scroll}
>
{/* 缓冲区:缓冲区是为了防止用户上滑或者下滑过程中,出现白屏等效果。(缓冲区和视图区为渲染真实的 DOM ) */}
<div className={styles.scroll_hold} style={{ height: `${start * itemHeight}px` }} />
<div className={styles.context} ref={context}>
{console.log('renderList', renderList)}
{renderList.map((item: any, index: number) => (
<div className={styles.list} key={index}>
{' '}
{item + ''} Item{' '}
</div>
))}
</div>
</div>
</div>
);
}
export default ResponsiveDemo;
css:
.list_box {
width: 400px;
height: 500px;
}
.list_box .scroll_box {
width: 400px;
position: relative;
overflow-y: scroll;
}
.list {
height: 40px;
width: calc(100% - 50px);
padding-left: 23px;
margin: 5px;
text-align: left;
line-height: 40px;
background-color: salmon;
border-radius: 40px;
color: white;
font-weight: bolder;
}
.context {
position: absolute;
top: 0;
width: 400px;
background: rgba(238, 255, 4, 0.5);
}
.scroll_hold {
background: rgba(136, 0, 255, 0.5);
position: absolute;
left: 0;
top: 0;
right: 0;
}
点个赞吧

浙公网安备 33010602011771号