react虚拟滚动

220 阅读1分钟
import React, { memo, useMemo, useRef, useState } from 'react';
import { PageContainer } from '@ant-design/pro-components';
import './MemberPage.less';

type Props = {
  // 滚动区域高度
  height: number;
  // 元素高度
  itemHeight: number;
}

const data = Array.from({ length: 1000000 }, (_, i) => 1 + i);

const VirtualScroll: React.FC<Props> = ({ itemHeight = 50, height = 500}) => {
  //可视区域高度
  const screenHeight = useRef(height);
  /** 列表总高度 */
  const listHeight = useRef(data.length * itemHeight);
  /** 显示元素数量 */
  const visibleCount = useRef(Math.ceil(screenHeight.current / itemHeight));

  //偏移量
  const [startOffset, setStartOffset] = useState(0);
  // 开始index
  const [startIndex, setStartIndex] = useState(0);
  // 结束index tips: 开始位置 + 显示元素数量
  const [endIndex, setEndIndex] = useState(startIndex + visibleCount.current);

  /** 当前需要渲染的元素内容 */
  const visibleData = useMemo(() => {
    return data.slice(startIndex, Math.min(endIndex, data.length));
  }, [startIndex, endIndex]);

  const getTransform = useMemo(() => {
    return `translate3d(0, ${startOffset}px,0)`;
  }, [startOffset]);

  const handleScroll = (e) => {
    // 当前滚动距离顶部高度
    const scrollTop = e.target.scrollTop;

    // 滚动高度 / 元素高度 = 当前滚动位置第一位元素下标
    const index = Math.floor(scrollTop / itemHeight);
    setStartIndex(index);
    setEndIndex(index + visibleCount.current);

    // 偏移量(滚动位置可能不能整除元素高度,需要设置一定位置的偏移量)
    setStartOffset(scrollTop - (scrollTop % itemHeight));
  }

  return (
    <PageContainer className='member_container'>
      <div className='container' onScroll={handleScroll}>
        {/* 滚动条 */}
        <div className='list-progress' style={{ height: listHeight.current + 'px' }} />
        {/* 滚动内容 */}
        <div className='list' style={{ transform: getTransform }}>
          {visibleData.map(item => <div key={item} className='list-item'>{item}</div>)}
        </div>
      </div>
    </PageContainer>
  );
};

export default memo(VirtualScroll);

css样式

.container {
  height: 500px;
  overflow: auto;
  position: relative;
  -webkit-overflow-scrolling: touch;
}
.list-progress {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  z-index: -1;
}
.list {
  left: 0;
  right: 0;
  top: 0;
  position: absolute;
  text-align: center;
}
.list-item {
  height: 50px;
}