js 实现虚拟滚动列表

前言

日常开发中,对于数据较多的列表我们多采用分页或者懒加载的方式实现。但现在有一个需要,要求前端展示10万条数据,你该如何实现呢?我们都知道dom过多会导致页面卡顿,那有没有办法解决呢?当然有,它就是虚拟滚动列表。那什么是虚拟滚动列表?它的原理又是什么呢?你可以简单理解为动态渲染可视区域数据的列表。我参考angular cdk虚拟滚动列表的实现方式写了一个简单的demo。感兴趣的小伙伴走过路过不要错过。话不多说,直接上代码。

实现

示意图

46521241-891dd380-c8b1-11e8-8ba4-774a062c7735.png

CSS

* {
    margin: 0;
    padding: 0;
}
.virtual-scroll-viewport {
    position: relative;
    width: 240px;
    height: 300px;
    margin: 150px auto 0;
    overflow: auto;
    will-change: scroll-position;
    border: 1px solid #aaaaaa;
}
/* 撑开高度 */
.virtual-scroll-spacer {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
}
.virtual-scroll-list {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
}
.virtual-scroll-item {
    height: 50px;
    padding-left: 15px;
    line-height: 50px;
}
.virtual-scroll-item:nth-child(2n) {
    background: #f6f8fa;
}
复制代码

HTML

<div class="virtual-scroll-viewport">
    <div class="virtual-scroll-spacer"></div>
    <div class="virtual-scroll-list"></div>
</div>
复制代码

JS

// 获取dom
const virtualViewportDom = document.querySelector('.virtual-scroll-viewport');
const virtualSpacerDom = document.querySelector('.virtual-scroll-spacer');
const virtualListDom = document.querySelector('.virtual-scroll-list');
const VIEWPORT_HEIGHT = 300; // 视口高度
const ITEM_HEIGHT = 50; // 列表项高度
const COUNT = 10; // 渲染数量
const TOTAL_COUNT = 100000; // 总条数
const TOTAL_HEIGHT = ITEM_HEIGHT * TOTAL_COUNT; // 总高度
let scrollTop = 0, translateY = 0;
virtualSpacerDom.style.height = TOTAL_HEIGHT + 'px';

// 监听scroll事件
virtualViewportDom.addEventListener('scroll', function (e) {
    scrollTop = this.scrollTop;
    const start = Math.max(Math.floor(scrollTop / ITEM_HEIGHT) - 2, 0);
    const end = Math.min(start + COUNT, TOTAL_COUNT);
    const y = start * ITEM_HEIGHT;
    if (scrollTop === 0 || scrollTop === TOTAL_HEIGHT - VIEWPORT_HEIGHT || Math.abs(translateY - y) >= 100) {
        render(start, end);
        translateY = y;
        virtualListDom.style.transform = 'translate3d(0px, ' + translateY + 'px, 0px)';
    }
});

// 初次渲染
render(0, COUNT);

function render(start, end) {
    let html = '';
    // 移除多余的dom
    let list = document.querySelectorAll('.virtual-scroll-item');
    for (const item of list) {
        const index = Number(item.dataset.index);
        if (index < start || index >= end) {
            item.remove();
        }
    }
    list = document.querySelectorAll('.virtual-scroll-item');
    if (list.length > 0) {
        const lastStart = Number(list[0].dataset.index);
        const lastEnd = Number(list[list.length - 1].dataset.index);
        if (end - 1 - lastEnd > 0) { // 下滑
            for (let i = lastEnd + 1; i < end; i++) {
                html += '<div class="virtual-scroll-item" data-index="' + i + '">item' + i + '</div>';
            }
            virtualListDom.insertAdjacentHTML('beforeend', html);
        } else if (start - lastStart < 0) { // 上滑
            for (let i = start; i < lastStart; i++) {
                html += '<div class="virtual-scroll-item" data-index="' + i + '">item' + i + '</div>';
            }
            virtualListDom.insertAdjacentHTML('afterbegin', html);
        }
    } else {
        // 快速滑动或拖动滚动条或初次渲染
        for (let i = start; i < end; i++) {
            html += '<div class="virtual-scroll-item" data-index="' + i + '">item' + i + '</div>';
        }
        virtualListDom.innerHTML = html;
    }
}
复制代码

Demo:jsdemo.codeman.top/html/virtua…

结语

至此,我们实现了一个简单的虚拟滚动列表。但还是存在一些不足,例如:

  1. 列表项并非动态高度
  2. 在移动端和某些浏览器上快速滑动会出现短暂空白

猜你喜欢

转载自juejin.im/post/7034352273694130212