美文网首页
列表虚加载

列表虚加载

作者: 欢西西西 | 来源:发表于2022-11-15 14:12 被阅读0次
  • html
<div class="box" id="box">
     <ul id="ul"></ul>
 </div>
  • css
.box {
    height: 500px;
    width: 400px;
    border: 2px solid blue;
    text-align: center;
    overflow: auto;
    padding-top: 20px;
    margin-top: 50px;
}

#ul {
    margin: 0;
    list-style: none;
    padding-left: 0;
}

#ul li {
    margin: 10px 5px 10px;
    padding: 5px 0 10px 0;
    border: 1px solid #000;
}   
  • VirtualScroll
class VirtualScroll {
    constructor({ scrollBox: box,
        listBox: ul,
        renderCount = 20, // 每次【最多】渲染N个,这里每批渲染的数量应该大于preDraw+每屏最多能放的卡片个数,不能设置太小
        list, // 全部数据
        onUptList, // 滚动时内部计算要渲染的数据传给onUptList
        onReachEnd // 滚动到最底触发onReachEnd
    }) {
        this.preDraw = 5; // 在可视范围显示的第一个卡片前面再展示N个
        this.btmConcatCount = 5; // 如果在触底后增加数据,则往下渲染N个
        this._resetProp();

        this.box = box;
        this.ul = ul;
        this.shift = ul.offsetTop;
        this.renderCount = renderCount;
        this.list = list;
        this.onUptList = onUptList;
        this.onReachEnd = onReachEnd;
        this._bindEvent();
        if (list.length) {
            this._onScroll();
        }
    }
    _resetProp() {
        this.itemHeights = []; // 存每个卡片到顶部占的高度
        this.startIndex = null;
        this.endIndex = null; // 不包含
        clearTimeout(this.scrollTimer);
        this.scrollTimer = null;
    }
    // 计算要渲染的数据应从哪项开始
    _getStartIndexOnScroll(scrollTop, arrHeight) {
        if (!arrHeight.length) {
            return 0;
        }
        let cur;
        for (let i = 0, lth = arrHeight.length; i < lth; i++) {
            if (scrollTop < arrHeight[i]) {
                cur = i;
                break;
            }
        }
        return Math.max(cur - this.preDraw + 1, 0);
        console.error('没找到合适的index');
        console.log('scrollTop:', scrollTop, arrHeight);
    }
    // 计算应渲染的数据 
    _getDrawList(allData, startIndex, endIndex) {
        let items = [];
        while (startIndex < endIndex) {
            let cur = allData[startIndex];
            items.push(cur);
            startIndex++;
        }
        return items;
    }
    _bindEvent() {
        this.box.addEventListener('scroll', () => {
            if (this.scrollTimer) {
                return;
            }
            this.scrollTimer = setTimeout(() => {
                this._onScroll();
                clearTimeout(this.scrollTimer);
                this.scrollTimer = null;
            }, 250);
        });
    }
    _isReachBtm() {
        let box = this.box;
        return box.scrollHeight - box.clientHeight - box.scrollTop < 50;
    }
    _onScroll() {
        let scrollTop = this.box.scrollTop;
        let box = this.box, list = this.list, itemHeights = this.itemHeights;

        let start = this._getStartIndexOnScroll(scrollTop, itemHeights);

        let calcCardsCount = itemHeights.length;

        if (start === this.startIndex) {
            if (calcCardsCount && this._isReachBtm() && calcCardsCount === list.length) { // 触底
                return this.onReachEnd();
            }
            return;
        }

        this.startIndex = start;
        this.endIndex = Math.min(list.length, start + this.renderCount);

        this._updateDrawList();

        calcCardsCount = itemHeights.length;
        if (calcCardsCount && this._isReachBtm()) { // 触底
            if (calcCardsCount === list.length) {
                return this.onReachEnd();
            }
        }
    }
    // 计算完startIndex和endIndex来更新列表
    _updateDrawList() {
        let start = this.startIndex,
            end = this.endIndex,
            itemHeights = this.itemHeights,
            calcCardsCount = itemHeights.length;

        let topSpace = start > 0 ? itemHeights[start - 1] - this.shift + 'px' : 0,
            bottomSpace = end < calcCardsCount ? itemHeights[calcCardsCount - 1] - itemHeights[end] + 'px' : 0;
        this.ul.style.paddingTop = topSpace;
        this.ul.style.paddingBottom = bottomSpace;

        // 传给调用者自己更新列表,这里应该同步更新,因为下一步可能要存height
        this.onUptList({
            list: this._getDrawList(this.list, start, end),
            topSpace,
            bottomSpace
        });

        if (calcCardsCount < end) { // 之前没有存过height的再存
            let i = start;
            Array.from(this.ul.childNodes).forEach((node) => {
                if (itemHeights[i] === undefined) {
                    itemHeights[i] = node.offsetHeight + node.offsetTop;
                }
                i++;
            });
        }
    }
    setData(list) { // 重新设置数据
        this._resetProp();
        this.list = list || [];
        if (!this.list.length) {
            return this.onUptList({
                list: [], topSpace: 0, bottomSpace: 0
            });
        }
        if (this.box.scrollTop > 0) {
            this.box.scrollTo(0, 0);
        } else {
            this._onScroll();
        }
    }
    addData(list) { // 拼接数据
        if (!list || !list.length) {
            return;
        }
        if (!this.list.length) {
            this.list = list;
            return this._onScroll();
        }
        this.list = this.list.concat(list);
        if (this._isReachBtm()) {
            this.endIndex = Math.min(this.list.length, this.startIndex + this.renderCount + this.btmConcatCount);
            this._updateDrawList();
        }
    }
}
  • 使用
// 生成假数据
function getMock(count) {
    let data = [], minHeight = 30;
    for (let i = 0; i < count; i++) {
        data[i] = {
            height: parseInt(minHeight + Math.random() * 100), // 随机行高
            // height: 30,
            color: `rgb(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255})`,
            text: i + 1
        }
    }
    return data;
}

// 如果每个li的上下margin不一样,这里滚动时也会抖动
let ul = document.getElementById('ul');


const v = new VirtualScroll({
    scrollBox: document.getElementById('box'),
    listBox: ul,
    renderCount: 15,
    list: getMock(50),
    onUptList({ list }) {
        ul.innerHTML = list.map(cur => `<li style='background-color: ${cur.color};line-height:${cur.height}px;'>
            注意卡片是否抖动:${cur.text} </li>`).join('');
    },
    onReachEnd() {
        console.log('加载下一页');
    }
});

相关文章

  • 列表虚加载

    html css VirtualScroll 使用

  • 给RecyclerView添加showLoadng、showEm

    通常来说加载一个列表的数据会有以下几种状态:加载中、加载失败,数据列表为空,正常的数据列表以及加载更多。在以前使用...

  • BFC引起的loading动画跳动BUG

    BUG现象 当列表加载时,loading动画在加载过程中出现跳动问题。 BUG原因 以下为列表结构: 当列表加载时...

  • nova命令使用

    加载配置文件: 查询虚机: 查看所有租户 重启虚机: 启动虚机:

  • filter导致列表加载时会发生抖动的BUG

    BUG现象 当列表加载时,整个列表结构会发生抖动问题。 BUG原因 当列表加载时,会给列表的内容加上一个遮罩层。表...

  • UITableView 复用时出现错乱问题

    列表通过网络加载数据的时候,不要将网络异步加载数据直接给列表项赋值。 原来做android项目开发的时候,在列表通...

  • React 高阶组件实践—通信录

    感谢老外一次分享 这种最终完成效果,加载一个用户列表 在成功完成加载列表前,添加一个加载画面提醒用户当前正在加载列...

  • 迭代/生成器

    for in列表一次性加载列表的所有数据到内存,而迭代器通过一次只加载一项数据,有效避免了当加载的列表过大导致内存...

  • vue自动触发事件

    项目中进入页面 先请求后台渲染一个列表,然后点击每个列表过后,再次请求后台加载该列表项下面的子列表,类似异步加载树...

  • vue上拉加载List 列表

    介绍 Vant框架瀑布流滚动加载,用于展示长列表,当列表即将滚动到底部时,会触发事件并加载更多列表项。 在main...

网友评论

      本文标题:列表虚加载

      本文链接:https://www.haomeiwen.com/subject/jkydxdtx.html