效能優化:虛擬列表,如何渲染10萬條資料的dom,頁面同時不卡頓
阿新 • • 發佈:2019-09-30
最近做的一個需求,當列表大概有2萬條資料,又不讓做成分頁,如果頁面直接渲染2萬條資料,在一些低配電腦上可能會照成頁面卡死,基於這個需求,我們來手寫一個虛擬列表
思路
- 列表中固定只顯示少量的資料,比如60條
- 在列表滾動的時候不斷的去插入刪除dom
- startIndex、endIndex,不斷的改變這個值來獲取最新的顯示列表
- paddingTop、paddingBottom撐開容器的滾動區域
首先看一下當直接插入2萬條列表時,頁面的效能
可以看到火焰圖中已經有了紅色的部分了,dom渲染也耗時也有1s多
再來看一下當使用虛擬列表時頁面的效能
從火焰圖中可以看出,火焰圖中一篇綠油油的,這就證明,通過虛擬列表來進行渲染使頁面效能得到了極大的提升
簡單的虛擬列表demo實現
我們假設有一個容器,高度為600px,列表項每個高度為30px,那麼根據列表的length我們就可以計算出滾動容器的總高度,也可以知道顯示60條資料的高度,我們此時可以給容器加一個paddingBottom,來撐開容器,來模擬頁面應該滾動的高度
this.paddingBottom = this.allHeight - this.scrollList.length * 30
容器同時還需要paddingTop用做當容器滾動頂部資料移除後撐起scrollTop
最後我們需要監聽容器的滾動事件來不斷的修改paddingTop、paddingBottom、startIndex、endIndex
最終效果
最後附上所有程式碼
<!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: 300px;
height: 600px;
overflow: auto;
border: 1px solid;
margin: 100px auto;
}
.item {
height: 29px;
line-height: 30px;
border-bottom: 1px solid #aaa;
padding-left: 20px;
}
</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">{{item}}</div>
</div>
</div>
</div>
<script src="./vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
list: [
'測試資料'
],
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(50000).fill(0)
arr = arr.map((item, index) => {
return index
})
this.list = [
...this.list,
...arr
]
}
}
})
</script>
</body>
</html>