性能優化:虛擬列表,如何渲染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