虚拟列表(基于vue2实现)

1,325 阅读1分钟

描述

【前端性能优化】使用虚拟列表优化长列表,本文基于vue2实现

思路

  1. 创建虚拟列表容器,容器内包括:
  • 显示列表(此列表截取数据list需要显示的部分+前后多展示若干项)
  • 一个空dom,高度根据数据list的项 * 每项高度算出,为了撑开父容器模拟滚动

2、监听虚拟列表容器滚动事件,调整显示列表的定位,使其永远覆盖在虚拟列表容器的上方

实现

<script>
const throttle = (fn, delay) => {
    let stratTimer
    let endTimer
    return function() {
        var args = Array.prototype.slice.call(arguments, 1)
        if (stratTimer) {
            return
        }
        stratTimer = setTimeout(() => {
            clearTimeout(stratTimer)
            stratTimer = null

            endTimer = setTimeout(() => {
                clearTimeout(endTimer)
                endTimer = null
                fn.apply(this, args)
            }, delay)
        }, delay)
        if (endTimer) {
            return
        }
        fn.apply(this, args)
    }
}

export default {
    data() {
        return {
            list: [],
            itemHeight: 20,
            size: 20,
            start: 0,
            end: 20,
            offset: 40, // 显示列表前后多预置的项目
        }
    },
    computed: {
        // 显示列表需要前后多预置多几项,防止滚动白屏问题
        showList() {
            const list = this.list.slice(this.start, this.end)
            // 显示列表预制
            // 列表前预制
            for (let i = 1; i <= this.offset; i++) {
                list.unshift(this.list[this.start-i] || '')
            }
            // 列表后预制,若已到达数据列表最底部,不再预制
            for (let i = 1; i <= this.offset; i++) {
                if (this.end + i <= this.list.length) {
                    list.push(this.list[this.end - 1 + i])
                }
            }
            return list
        }
    },
    methods: {
        handleScroll: throttle(function() {
            const scrollTop = this.$refs.scroll.scrollTop
            this.start = Math.floor(scrollTop / this.itemHeight)
            this.end = this.start + this.size
        }, 100)
    },
    mounted() {
        for (let i = 1; i <= 10000; i++) {
            this.list.push(i)
        }
    }
}
</script>

<template>
    <div ref="scroll" class="vs-list-container" :style="{height: itemHeight * size + 'px'}" @scroll="handleScroll">
        <div class="vs-list-scroll" :style="{height: itemHeight * list.length + 'px'}"></div>
        <div class="vs-list" :style="{top: itemHeight * (start - offset) + 'px'}">
            <div class="vs-list-item" v-for="(item, index) in showList" :key="index">{{item}}</div>
        </div>
    </div>
</template>

<style scoped>
.vs-list-container {
    position: relative;
    box-sizing: border-box;
    width: 200px;
    border: solid 1px #000000;
    overflow-y: auto;
}

.vs-list {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
}
.vs-list-item {
    height: 20px;
    width: 100%;
}
</style>

存在问题

1、虚拟列表快速拖动白屏问题,通过在显示列表前后多预置若干项来解决

2、未解决:虚拟列表瞬间拖动到底部还是存在白屏问题,原因是显示列表预制的项没有拖动的高度多