React虚拟列表

352 阅读1分钟
    const [dataList, setDataList] = React.useState([]); /* 保存数据源 */
    const [position, setPosition] = React.useState([
        0, 0,
    ]); /* 截取缓冲区 + 视图区索引 */
    const scroll = React.useRef(null); /* 获取scroll元素 */
    const box = React.useRef(null); /* 获取元素用于容器高度 */
    const context = React.useRef(null); /* 用于移动视图区域,形成滑动效果。 */
    const scrollInfo = React.useRef({
        height: 500 /* 容器高度 */,
        bufferCount: 8 /* 缓冲区个数 */,
        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 dataList = new Array(10000).fill(1).map((item, index) => index + 1);
        setDataList(dataList);
        setPosition([0, renderCount]);
    }, []);
    const handleScroll = () => {
        const { scrollTop } = scroll.current;
        const { itemHeight, renderCount } = scrollInfo.current;
        const currentOffset = scrollTop - (scrollTop % itemHeight);
        const start = Math.floor(scrollTop / itemHeight);
        context.current.style.transform = `translate3d(0, ${currentOffset}px, 0)`; /* 偏移,造成下滑效果 */
        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); /* 渲染区间 */
    console.log('渲染区间', position);

    return (
        <div className="list_box" ref={box}>
            <div
                className="scroll_box"
                style={{ height: height + 'px' }}
                onScroll={handleScroll}
                ref={scroll}
            >
                <div
                    className="scroll_hold"
                    style={{ height: `${dataList.length * itemHeight}px` }}
                />

                <div className="context" ref={context}>
                    {renderList.map((item, index) => (
                        <div className="list" key={index}>
                            {item + ''} Item{' '}
                        </div>
                    ))}
                </div>
            </div>
        </div>
    );
}

对应scss


.list {
  list-style: none;
  background-color: #fc4838;
  padding: 10px 20px;
  color: #fff;
  height: 50px;
  line-height: 50px;
  box-sizing: border-box;
  margin-bottom: 10px;
  margin-left: 24px;
  margin-right: 24px;
  font-weight: bold;
  border-radius:10px ;
}

.list_box {
  position: fixed;
  left:0;
  top:60px;
  overflow: scroll;
  bottom:0;
  right: 0;
}

.scroll_hold {
  position: absolute;
  left:0;
  top:0;
  right: 0;
}

.scroll_box {
  overflow: scroll;
  position: relative;
}

DEMO: jk9xd5.csb.app/