vue超长列表性能优化

2,694 阅读1分钟

概述

背景

当我们遇到一个列表页面,数据量特别大(例如数据量为1万条数据),并且页面采用是滑动上拉分页时,用户会感觉到页面滑动卡顿、数据渲染较慢的问题,这个时候需要对长列表进行性能优化

思路

可以通过按需进行加载dom,即只显示可视化区域的数据,不在可视区域的数据只做dom的占位处理,不显示数据,从而减少减少dom的结构, 实现性能提升

实现

方式1-不封装组件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.14/vue.js"></script>
</head>
<style>
.sroll-box{
    box-sizing: border-box;
    overflow: auto;
    border: 1px solid #666;
}
.scroll-item{
    border-bottom: 1px solid #666;
    box-sizing: border-box;
}
</style>
<body>
    <div id="app">
        <div class="sroll-box" ref="box" @scroll="handleScroll" :style="{
            height: visibleCount*itemHeight + 'px'
        }">
            <div class="scroll-list" :style="{ 
                height: list.length * itemHeight + 'px',  transform: `translateY(${fixedScrollTop}px)`
            }">
                <div class="scroll-item" v-for="item in visibleData" :style="{height:itemHeight + 'px', 'line-height': itemHeight + 'px'}">
                    {{item.value}}
                </div>
            </div>
        </div>
    </div>
</body>
<script>
    new Vue({
      el: '#app',
      data(){
        //生成数据
        let list = []
        for(var i=0;i<500;i++){
            list.push({id:i,value:i+1})
        }
        return {
            list, //数据列表
            itemHeight: 30,//每条记录的高度
            visibleCount: 10,//可视化区域,渲染个数
            start: 0, //可视区域,渲染数据开始索引值
            end: 0,//可视化区域,渲染数据结束索引值
            visibleData: [], //可视化区域渲染的数据列表
            fixedScrollTop: 0//偏移高度
        }
      },
      mounted() {
          // 可视化高度
          let clientHeight = this.$refs.box.clientHeight
          //初始化数据
          this.visibleCount = Math.ceil(clientHeight / this.itemHeight)
          this.start = 0
          this.end = this.start + this.visibleCount
          this.visibleData = this.list.slice(this.start, this.end)
      }, 
      methods: {
        handleScroll(){
            const scrollTop = this.$refs.box.scrollTop;//获取 dom 滚动高度
            const offsetNumber = Math.floor(scrollTop / this.itemHeight); //计算偏移个数
            this.fixedScrollTop = offsetNumber * this.itemHeight; //计算偏移高度
            //计算开始 结束 数据
            this.start = offsetNumber;
            this.end = this.start + this.visibleCount;
            this.visibleData = this.list.slice(this.start, this.end);
        }
      } 
    })
</script>
</html>

预览

链接

方式2-封装组件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.14/vue.js"></script>
</head>
<style>
.sroll-box{
    box-sizing: border-box;
    overflow: auto;
    border: 1px solid #666;
}
.scroll-item{
    border-bottom: 1px solid #666;
    box-sizing: border-box;
}
</style>
<body>
    <div id="app">
        <virtual-list :list="list" :item-height="40" :visible-count="15"></virtual-list>
    </div>
</body>
<script>
    //组件virtual-list
    Vue.component('virtual-list', {
        template: `
            <div class="sroll-box" ref="box" @scroll="handleScroll" :style="{
                height: visibleCount*itemHeight + 'px'
            }">
                <div class="scroll-list" :style="{ 
                    height: list.length * itemHeight + 'px',  transform: 'translateY('+ fixedScrollTop+ 'px)'
                }">
                    <div class="scroll-item" v-for="item in visibleData" :style="{height:itemHeight + 'px', 'line-height': itemHeight + 'px'}">
                        {{item.value}}
                    </div>
                </div>
            </div>
        `,
        props: {
            list: {
                type: Array,//数据列表
                default: []
            },
            itemHeight: {//每条记录的高度
                type: Number,
                default: 30
            },
            visibleCount: {//可视化区域,渲染个数
                type: Number,
                default: 10
            }
        },
        data(){
            return {
                start: 0, //可视区域,渲染数据开始索引值
                end: 0,//可视化区域,渲染数据结束索引值
                visibleData: [], //可视化区域渲染的数据列表
                fixedScrollTop: 0//偏移高度
            }
        },
        mounted() {
          // 可视化高度
          let clientHeight = this.$refs.box.clientHeight
          //初始化数据
          this.visibleCount = Math.ceil(clientHeight / this.itemHeight)
          this.start = 0
          this.end = this.start + this.visibleCount
          this.visibleData = this.list.slice(this.start, this.end)
        },
        methods: {
            handleScroll(){
                const scrollTop = this.$refs.box.scrollTop;//获取 dom 滚动高度
                const offsetNumber = Math.floor(scrollTop / this.itemHeight); //计算偏移个数
                this.fixedScrollTop = offsetNumber * this.itemHeight; //计算偏移高度
                //计算开始 结束 数据
                this.start = offsetNumber;
                this.end = this.start + this.visibleCount;
                this.visibleData = this.list.slice(this.start, this.end);
            }
        } 
    })
    new Vue({
      el: '#app',
      data(){
        //生成数据
        let list = []
        for(var i=0;i<500;i++){
            list.push({id:i,value:i+1})
        }
        return {
            list           
        }
      }
    })
</script>
</html>

预览

链接

其他实现

可以使用npm中实现这个功能的第三方包,比如lang-virtual-list-vuevue-virtual-scroll-listvue-virtualized-list