前端简单的列表虚拟滚动实现

81 阅读1分钟

场景:在列表页面分页加载较多数据后,数据的堆积量会造成一定的性能影响,特别是对于列表样式元素较多的情况下尤为明显,所以想分享一下基本的列表虚拟滚动实现。

一、计算滚动高度:单条列表高度 * 列表数据总长度(total,一般由列表接口返回查出total字段)。

for(let i = 1; i <= 9999; i++){
            this.list.push(i);
        }
        this.total = this.list.length; //这里只是假写,正常的话叫后端查出列表数据长度,返回给前端total。
        this.total_height = this.total * this.itemHeight; //列表数据长度 * 每个列表项高度 = 总容器高度。
}

![)405S)%Q)WNX)G@S%)CVVR.png

如上图所示,就计算出了总体的高度。

二、创建一个父元素包裹第一步写的滚动高度块,固定高度和overflow-y: scroll。

![LK2}H]IR}`I4FFWGXP3ZZYX.png](p9-juejin.byteimg.com/tos-cn-i-k3…?)

这样我们就可以进行滚动列表了。

(重点)三、虚拟列表滚动就类似于分页查询,始终只带出数条数据,将这种逻辑带入到页面dom层上,除去可视区域之外的列表dom,就可以带来有效的性能提升。

滚动计算逻辑如下:

mounted(){
        // 挂载完毕就要调用一次,进行列表初始化slice(0,10)渲染头十条数据。
        this.updateVisibleData();
},
methods: {
    handleScroll(){
        this.scrollTop = this.$refs.mScroll.scrollTop;
        this.updateVisibleData();
    },
    updateVisibleData(){
        //滚动计算
        let startIndex = this.scrollTop == 0 ? 0 : Math.floor(this.scrollTop / this.itemHeight);
        let endIndex = startIndex + Math.ceil(this.$refs.mScroll.offsetHeight / this.itemHeight);
        this.newArr = this.list.slice(startIndex, endIndex);
    }
}

完整演示代码:

<template>
    <div class="mScroll" ref="mScroll" @scroll="handleScroll">
        <div class="virtual-scroll" :style="{ 'height': `${total_height}px` }"></div>
        <div class="list-box">
            <div class="item" v-for="(item,index) in newArr" :key="index">项目:{{ item }}</div>
        </div>
    </div>
</template>

<script>
export default{
    data(){
        return{
            list: [], //列表数据
            scrollTop: 0, //滚动距离
            itemHeight: 70, //每个列表高度,用于计算
            total: 0,
            total_height: 0,
            newArr: []
        }
    },
    created(){
        for(let i = 1; i <= 9999; i++){
            this.list.push(i);
        }
        this.total = this.list.length; //这里只是假写,正常的话叫后端查出列表数据长度,返回给前端total。
        this.total_height = this.total * this.itemHeight; //列表数据长度 * 每个列表项高度 = 总容器高度。
    },
    mounted(){
        // 挂载完毕就要调用一次,进行列表初始化slice(0,10)渲染头十条数据。
        this.updateVisibleData();
    },
    methods: {
        handleScroll(){
            this.scrollTop = this.$refs.mScroll.scrollTop;
            this.updateVisibleData();
        },
        updateVisibleData(){
            //滚动计算
            let startIndex = this.scrollTop == 0 ? 0 : Math.floor(this.scrollTop / this.itemHeight);
            let endIndex = startIndex + Math.ceil(this.$refs.mScroll.offsetHeight / this.itemHeight);
            this.newArr = this.list.slice(startIndex, endIndex);
        }
    }
}
</script>

<style scoped>
body{
    overflow: hidden;
}
.mScroll{
    width: 500px;
    height: 700px;
    /* margin: 0 auto; */
    position: relative;
    overflow-y: scroll;
}
.item{
    width: 100%;
    height: 50px;
    margin-bottom: 20px;
    border: 1px solid #666;
    display: flex;
    justify-content: center;
    align-items: center;
    box-sizing: border-box;
}
.list-box{
    width: 480px;
    position: fixed;
    left: 0;
    top: 0;
    z-index: -1;
}
</style>