最近做的一個需求,當列表大概有5萬條數據,又不讓做成分頁,如果頁面直接渲染5萬條數據,在一些低配電腦上可能會照成頁面卡死,基於這個需求,研究分析前端渲染卡頓原因,准備手寫一個虛擬列表。
1.實現思路
- 列表中固定只顯示少量的數據,比如60條
- 在列表滾動的時候不斷的去插入刪除dom
- startIndex、endIndex,不斷的改變這個值來獲取最新的顯示列表
- 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節點,會導致渲染慢,而且滾動起來非常卡頓,通過虛擬列表的方式,大大地提高了渲染效率和滾動流暢度!