性能优化:虚拟列表,如何渲染5万条数据的dom,页面同时不卡顿


最近做的一个需求,当列表大概有5万条数据,又不让做成分页,如果页面直接渲染5万条数据,在一些低配电脑上可能会照成页面卡死,基于这个需求,研究分析前端渲染卡顿原因,准备手写一个虚拟列表。

1.实现思路

  1. 列表中固定只显示少量的数据,比如60条
  2. 在列表滚动的时候不断的去插入删除dom
  3. startIndex、endIndex,不断的改变这个值来获取最新的显示列表
  4. paddingTop、paddingBottom撑开容器的滚动区域

首先看一下当直接插入10万条列表时,页面的性能。

 

2.方式对比

情况一

  • 直接渲染出5万条数据

 

可以看到火焰图中已经有了红色的部分了,dom渲染也耗时也有5s多 

  • 优化前效果

 

 

情况二

  • 采用虚拟列表加载方式

假设有一个容器,高度为600px,列表项每个高度为30px,那么根据列表的length我们就可以计算出滚动容器的总高度,也可以知道显示60条数据的高度,我们此时可以给容器加一个paddingBottom,来撑开容器,来模拟页面应该滚动的高度

this.paddingBottom = this.allHeight - this.scrollList.length * 30

容器同时还需要paddingTop用做当容器滚动顶部数据移除后撑起scrollTop

最后我们需要监听容器的滚动事件来不断的修改paddingTop、paddingBottom、startIndex、endIndex。

 

 调整为虚拟列表之后,可以看到加载渲染在1秒左右!

 

  • 优化后效果

3.实现代码

<!doctype html>
<html lang="zh">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport"
            content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
        <style>
            .container {
                width: 500px;
                height: 600px;
                overflow: auto;
                border: 1px solid;
                margin: 100px auto;
            }

            .item {
                height: 29px;
                line-height: 30px;
                border-bottom: 1px solid #aaa;
                padding-left: 20px;
                display: flex;
                text-align: center;
            }
            .item span{
                flex: 1;
            }
        </style>
    </head>
    <body>
        <div id="app">
            <button @click="add">添加数据</button>
            <div class="container" ref="container">
                <div class="scroll-wrapper" :style="style">
                    <div v-for="(item, index) in scrollList" :key="index" class="item">
                        <span>{{item.name}}</span>
                        <span>{{item.price}}</span>
                        <span>{{item.category}}</span>
                    </div>
                </div>
            </div>
        </div>
        <script src="./js/vue.js"></script>
        <script>
            new Vue({
                el: '#app',
                data: {
                    list: [
                        {name:'名称',price:'单价',category:'类型'}
                    ],
                    startIndex: 0,
                    endIndex: 60,
                    paddingTop: 0,
                    paddingBottom: 0,
                    allHeight: 0
                },
                computed: {
                    scrollList() {
                        return this.list.slice(this.startIndex, this.endIndex)
                    },
                    style() {
                        return {
                            paddingTop: this.paddingTop + 'px',
                            paddingBottom: this.paddingBottom + 'px'
                        }
                    }
                },
                watch: {
                    list(val) {
                        const valLen = val.length
                        this.allHeight = valLen * 30
                        this.paddingBottom = this.allHeight - this.scrollList.length * 30
                    }
                },
                mounted() {
                    const container = this.$refs.container
                    container.addEventListener('scroll', () => {
                        const top = container.scrollTop
                        this.startIndex = Math.floor(top / 30)
                        this.endIndex = this.startIndex + 60

                        this.paddingTop = top
                        if (this.endIndex >= this.list.length - 1) {
                            this.paddingBottom = 0
                            return
                        }
                        this.paddingBottom = this.allHeight - 600 - top
                    })
                },
                methods: {
                    add() {
                        let arr = new Array(100000).fill(0)
                        arr = arr.map((item, index) => {
                            return { name: "名称_" + index, price: Math.ceil(Math.random()*10)+'', category: Math.random() > 0.5 ? '蔬菜' : '水果' }
                        })
                        this.list = [
                            ...this.list,
                            ...arr
                        ]
                    }
                }
            })
        </script>
    </body>
</html>

 总结:由于不断操作dom节点,会导致渲染慢,而且滚动起来非常卡顿,通过虚拟列表的方式,大大地提高了渲染效率和滚动流畅度!


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM