简单的虚拟滚动实现

106 阅读1分钟

先看dom标签接口分为两层, 外层box容器用来做滚动容器, 内层hold容器撑开外层box使其产生滚动条, 内层view容器用来展示内容

<body>
    <div id="box">
        <div id="hold"></div>
        <div id="view">
        </div>
    </div>
</body>

样式无非就是调整定位

 body,html {
    width: 100%;
    height: 100%;
    padding: 0;
    margin: 0;
}
#view{
    width: 100%;
}
#view>div:hover {
    background: rgb(240, 240, 240);
}

#box {
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    height: 500px;
    width: 200px;
    border: 1px solid black;
    overflow: auto;
}

#box>div {
    font-size: 14px;
    cursor: pointer;
    user-select: none;
}

/* 以上单纯用来装饰使用的样式 */

#box {
    /* 外层容器需要使用定位属性配合内部的 absolute实现定位*/
    /* 超出后出现滚动条 */
    position: absolute;
    overflow: auto;
}

#view {
    /* 定位属性通过js一直控制view容器展示在页面上 */
    position: absolute;
    top: 0;
}

#view>div {
    /* 行高要统一才能计算准确 */
    height: 20px;
    line-height: 20px;
}

大致的逻辑伪代码如下 真实的计算逻辑就不写了因为懒

    // 基础行高
    const lineHeight = 20
    // 基础数据
    const data = new Array(10000000).fill('').map((a, i) => i)
    // 整体的容器
    const box = document.querySelector('#box')
    // 实际展示的容器
    const view = document.querySelector('#view')
    // 用来撑开容器的dom
    const hold = document.querySelector('#hold')
    // 根据数据量*行高计算高度进行填充
    hold.style.height = lineHeight * data.length + 'px'

    const render = (e) => {
        // 初始化高度
        let top = 0
        try {
            // 直接读取高度报错就为0
            top = e.target.scrollTop
        } catch {}
        // 设置页面容器的顶部定位
        view.style.top = top + 'px'
        // 获取数据位置的起点
        const start = (top / lineHeight).toFixed(0)
        // 获取页面展示的内容数量
        const n = box.clientHeight / lineHeight
        // 提取数据
        const viewData = data.slice(start, start * 1 + n)
        // 渲染
        view.innerHTML = viewData.map(v => `<div>${v}</div>`).join('')
    }

    box.addEventListener('scroll', render)
    render()