react中实现瀑布流虚拟列表

通常在瀑布流中每次拉取下一页数据需要渲染大量数据的时候是非常耗时,会出现卡顿的现象。随着拉取的数据越来越多,列表渲染时间长、卡顿的问题越来越严重。

这个时候虚拟列表就派上用场了。虚拟列表的实现原理简单来说,就是列表并不会把所有的数据都渲染出来,而是通过监听滚动事件然后实时计算当前是哪几条数据显示在页面上,然后只渲染用户可以看见的这几条数据

效果截图:

可以看到,我生成了60个元素,但初始化渲染的时候容器只渲染了可看见的20个元素。那么我们在滚动一下滚动条看看效果。

滚动条处于在容器的中间的位置,我再次重新获取了父容器 children元素后,结果还是之渲染20个元素。虚拟列表效果是不是就实现了。

话不多说,直接给展示全部代码,里面有备注代码讲解!。可以直接复制粘贴到html文件中,方便你们预览效果。

以下是全部代码:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 引入React -->
    <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
    <!-- 引入React DOM -->
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
    <!-- 引入Babel,用于JSX转换 -->
    <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
    <style>
        .list-container {
            overflow: auto;
            box-sizing: border-box;
            border: 1px solid black;
            height: 800px;
            width: 1300px;
            margin: 30px;

        }

        .list-container-inner {
            display: flex;
            flex-wrap: wrap;
            gap: 16px;
        }

        .list-container-inner-item {
            width: 243px;
            height: 370px;
            outline: 1px solid black;
        }

        .inner-img {
            height: 86%;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        .inner-img-text {
            box-shadow: 0px 0px 6px 1px #fff;
            padding: 8px;
        }
    </style>
</head>

<body>
    <div id="root"></div>
</body>
<script type="text/babel">
    function generateRandomColor() {//生成随机颜色
        var letters = '0123456789ABCDEF';
        var color = '#';
        for (var i = 0; i < 6; i++) {
            color += letters[Math.floor(Math.random() * 16)];
        }
        return color;
    }
    function createData() {//生成数据
        const data = []
        for (let i = 0; i < 60; i++) {
            data.push({ id: i, content: `内容:${i}`, color: generateRandomColor() })
        }
        return data
    }

    const App = () => {
        let [data, setData] = React.useState(createData)
        let [virtualData, setVirtualData] = React.useState([])
        let [_paddings, setPaddings] = React.useState([0, 0])

        React.useEffect(() => {
            let itemHeight = 370
            let itemrWidth = 243
            let outerWidth = 1300
            let outerHeight = 800
            const outerContainer = document.querySelector('.list-container')

            // 把获取的数据,按每行能放置多少个元素进行坐标分组
            let twoDimensionalCoordinates = []
            function getCoordinate() {
                let start = 0
                let init_end = Math.floor(((outerWidth - (((outerWidth / itemrWidth) - 1) * 16)) / itemrWidth))// 计算外部容器一行能放多少个元素
                let end = init_end

                while (true) {
                    if (data.slice(start, end).length > 0) {
                        twoDimensionalCoordinates.push([start, end])
                    } else {
                        break;
                    }
                    start = end
                    end += init_end
                }
            }
            getCoordinate()

            const scrollCallback = () => {
                // 获取当前要渲染的元素的坐标
                const scrollTop = Math.max(outerContainer.scrollTop, 0)
                const startIndex = Math.floor(scrollTop / itemHeight)
                const endIndex = startIndex + Math.ceil(outerHeight / itemHeight)

                // 从twoDimensionalCoordinates取出要渲染的元素并渲染到容器中
                const viewData = twoDimensionalCoordinates.slice(startIndex, endIndex + 1)
                const escapeData = viewData.reduce((pre, coordinate) => pre.concat(data.slice(coordinate[0], coordinate[1])), [])
                setVirtualData(escapeData)

                // 未渲染的元素由padding-top和padding-bottom代替,保证滚动条位置正确
                const paddingTop = startIndex * itemHeight
                const paddingBottom = (twoDimensionalCoordinates.length - endIndex) * itemHeight
                setPaddings([paddingTop, paddingBottom])
            }

            // 首屏渲染
            scrollCallback()

            // 监听外部容器的滚动事件
            outerContainer.addEventListener('scroll', scrollCallback)

            return () => {
                outerContainer.removeEventListener('scroll', scrollCallback)
            }
        }, [])

        return <div className="list-container">
            <div className='list-container-inner' style={{ paddingTop: _paddings[0], paddingBottom: _paddings[1] }}>
                {virtualData.map((item) => {
                    return <div className='list-container-inner-item' key={item.id}>
                        <div className='inner-img' style={{ background: item.color }}>
                            <span className="inner-img-text">img容器</span>
                        </div>
                        <div>
                            <div>标题-{item.content}</div>
                            <div>{item.content}</div>
                        </div>
                    </div>
                })}
            </div>
        </div>
    }

    // 挂载React应用
    ReactDOM.render(<App />, document.getElementById('root'));
</script>

</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值