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;
}
posted @ 2025-02-17 16:02  码磊姐姐  阅读(184)  评论(0)    收藏  举报