实现基础版虚拟列表

1 阅读1分钟

列表展示是前端一种常见的渲染方式,但是当数据量过多的时候怎么办呢?是把所有数据都渲染出来吗?这样页面上的元素过多会造成页面卡顿,影响用户体验,有没有更好的方式呢?这里我们想到了虚拟列表,即展示给用户的是一个虚拟的长列表,其实我们渲染的只是截取的其中一部分的数据,当滚动的时候及时更新这些数据,这样既可以展示所有数据又可以减少DOM元素

基础版虚拟列表的实现

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>基础列表</title>
    </head>
    <style>
        #container {
            position: relative;
            width: 500px;
            height: 500px;
            border: 1px solid #000;
            overflow-y: scroll;
        }
        #placeholder {
            width: 1px;
            position: absolute;
            top: 0;
            left: 0;
        }
        #list {
            width: 100%;
            position: absolute;
            top: 0;
            left: 0;
            box-sizing: border-box;
        }

    </style>
    <body>
        <!-- 容器:可视窗口 -->
        <div id="container">
            <!-- 数据展示列表 -->
            <div id="list"></div>
            <!-- 模拟滚动条 -->
            <div id="placeholder"></div>
        </div>
        
    </body>
    <script>
        // 获取元素
        let container = document.getElementById('container');
        let list = document.getElementById('list');
        let placeholder = document.getElementById('placeholder');

        // 模拟数据多数据
        let mockData = [];
        for (let i = 0; i < 1000; i++) {
            mockData.push({
                label: `这是第${i}条数据`,
                value: i
            });
        }

        // 配置项
        let config = {
            itemHight: 30, // 每一项的高度
            moreNum: 2, // 多渲染的数量
        }

        // 获取容器高度,计算渲染数量
        let containerHeight = container.clientHeight;
        // 计算渲染的item的数量
        let allNum = Math.ceil(containerHeight/config.itemHight) + config.moreNum
        // 设置模拟滚动条div的高度,让其等于总数据高度,以便出现正确的滚动条
        placeholder.style.height = mockData.length * config.itemHight + 'px';

        // 加了防抖的效果,性能更好
        // container.addEventListener('scroll', debounce(resetPosition, 30))
        // 不加防抖的效果
        container.addEventListener('scroll', resetPosition)
        resetPosition()
        // 渲染函数
        function resetPosition() {
            // 获取容器的滚动高度,方便后面定位列表,使列表始终展示在视窗内
            let scrollTop = container.scrollTop;
            // 根据滚动高度计算开始和结束的index,从而截取对应的数据进行渲染
            let startIndex = Math.floor(scrollTop / config.itemHight) // 开始index
            let endIndex = startIndex + allNum; // 结束index = 开始index + 渲染数量
            let renderData = mockData.slice(startIndex, endIndex); // 需要渲染的数据
            // 将列表定位到可视窗口的位置
            list.style.top = startIndex * config.itemHight + 'px';
            // 渲染列表
            let nodes = ''
            renderData.forEach(item => {
                nodes += `<div style="height: ${config.itemHight}px">${item.label}</div>`
            })
            list.innerHTML = nodes;
        }

        // 防抖函数
        function debounce(fun, wait) {
            let timeout
            return function () {
                clearTimeout(timeout)
                timeout = setTimeout(() => {
                    fun.apply(this, arguments)
                }, wait)
            }
        }
    </script>
    </html>