在工作中,有時會遇到需要一些不能使用分頁方式來加載列表數據的業務情況,對於此,我們稱這種列表叫做長列表。比如,在一些外匯交易系統中,前端會實時的展示用戶的持倉情況(收益、虧損、手數等),此時對於用戶的持倉列表一般是不能分頁的。
那麼我們應該怎麼操作才能夠提高瀏覽器渲染的性能呢?下面是一種具體的做法,引用的是雲中橋的文章高性能渲染十萬條數據。
- 什麼是虛擬列表
虛擬列表其實是按需顯示的一種實現,即只對可見區域進行渲染,對非可見區域中的數據不渲染或部分渲染的技術,從而達到極高的渲染性能。
假設有1萬條記錄需要同時渲染,我們屏幕的可見區域的高度爲500px,而列表項的高度爲50px,則此時我們在屏幕中最多隻能看到10個列表項,那麼在首次渲染的時候,我們只需加載10條即可。
說完首次加載,再分析一下當滾動發生時,我們可以通過計算當前滾動值得知此時在屏幕可見區域應該顯示的列表項。
假設滾動發生,滾動條距頂部的位置爲150px,則我們可得知在可見區域內的列表項爲第4項至`第13項。
實現
虛擬列表的實現,實際上就是在首屏加載的時候,只加載可視區域內需要的列表項,當滾動發生時,動態通過計算獲得可視區域內的列表項,並將非可視區域內存在的列表項刪除。
- 計算當前可視區域起始數據索引(startIndex)
- 計算當前可視區域結束數據索引(endIndex)
- 計算當前可視區域的數據,並渲染到頁面中
- 計算startIndex對應的數據在整個列表中的偏移位置startOffset並設置到列表上
由於只是對可視區域內的列表項進行渲染,所以爲了保持列表容器的高度並可正常的觸發滾動,將Html結構設計成如下結構:
<div class="infinite-list-container">
<div class="infinite-list-phantom"></div>
<div class="infinite-list">
<!-- item-1 --> <!-- 你要進行v-for的地方>
<!-- item-2 -->
<!-- ...... -->
<!-- item-n -->
</div>
</div>
infinite-list-container
爲可視區域的容器
infinite-list-phantom
爲容器內的佔位,高度爲總列表高度,用於形成滾動條
infinite-list
爲列表項的渲染區域
接着,監聽infinite-list-container
的scroll
事件,獲取滾動位置scrollTop
假定可視區域高度固定,稱之爲screenHeight
假定列表每項高度固定,稱之爲itemSize
假定列表數據稱之爲listData
假定當前滾動位置稱之爲scrollTop
則可推算出:
- 列表總高度
listHeight = listData.length * itemSize
- 可顯示的列表項數
visibleCount = Math.ceil(screenHeight / itemSize)
- 數據的起始索引
startIndex = Math.floor(scrollTop / itemSize)
- 數據的結束索引
endIndex = startIndex + visibleCount
- 列表顯示數據爲
visibleData = listData.slice(startIndex,endIndex)
當滾動後,由於渲染區域相對於可視區域已經發生了偏移,此時我需要獲取一個偏移量startOffset,通過樣式控制將渲染區域偏移至可視區域中。
- 偏移量
startOffset = scrollTop - (scrollTop % itemSize);
好了準備工作介紹完畢,現在開始封裝組件,下面是封裝的代碼。
<template>
<div ref="list" class="infinite-list-container" @scroll="scrollEvent($event)">
<div class="infinite-list-phantom" :style="{ height: listHeight + 'px' }"></div>
<div class="infinite-list" :style="{ transform: getTransform }">
<div ref="items"
class="infinite-list-item"
v-for="item in visibleData"
:key="item.id"
:style="{ height: itemSize + 'px',lineHeight: itemSize + 'px' }"
>{{ item.value }}</div>
</div>
</div>
</template>
<script>
export default {
name:'VirtualList',
props: {
//所有列表數據
listData:{
type:Array,
default:()=>[]
},
//每項高度
itemSize: {
type: Number,
default:200
}
},
computed:{
//列表總高度
listHeight(){
return this.listData.length * this.itemSize;
},
//可顯示的列表項數
visibleCount(){
return Math.ceil(this.screenHeight / this.itemSize)
},
//偏移量對應的style
getTransform(){
return `translate3d(0,${this.startOffset}px,0)`;
},
//獲取真實顯示列表數據
visibleData(){
return this.listData.slice(this.start, Math.min(this.end,this.listData.length));
}
},
mounted() {
this.screenHeight = //this.$el.clientHeight; 這裏修改爲你那個列表可視的高度
this.start = 0;
this.end = this.start + this.visibleCount;
},
data() {
return {
//可視區域高度
screenHeight:0,
//偏移量
startOffset:0,
//起始索引
start:0,
//結束索引
end:null,
};
},
methods: {
scrollEvent() {
//當前滾動位置
let scrollTop = this.$refs.list.scrollTop;
//此時的開始索引
this.start = Math.floor(scrollTop / this.itemSize);
//此時的結束索引
this.end = this.start + this.visibleCount;
//此時的偏移量
this.startOffset = scrollTop - (scrollTop % this.itemSize);
}
}
};
</script>
<style scoped>
.infinite-list-container {
height: 100%;
overflow: auto;
position: relative;
-webkit-overflow-scrolling: touch;
}
.infinite-list-phantom {
position: absolute;
left: 0;
top: 0;
right: 0;
z-index: -1;
}
.infinite-list {
left: 0;
right: 0;
top: 0;
position: absolute;
text-align: center;
}
.infinite-list-item {
padding: 10px;
color: #555;
box-sizing: border-box;
border-bottom: 1px solid #999;
}
</style>
listData
、itemSize
分別是你想傳入的數據、每一項數據的高度,.infinite-list-container
要記得設置高度(不一定是100%,也可以是你想要設置px的高度),.infinite-list-item
可以設置爲你想要的樣式,其他樣式要保持一致。
demo在這裏https://codesandbox.io/s/virtuallist-1-rp8pi
好了,以上就是固定寬高渲染十萬條數據的方法,不固定寬高的可以看看上面那個作者的文章。
--------------------------------------------------------------------下面介紹如下增加一個搜索框搜索這些數據
2020年4月3日新增搜索關鍵詞高亮功能
<input type="text" v-model="search">
data:{
return{
search:''
}
}
//只需要修改一下computed和添加watch
computed:{
replaceArr () {
const listData = JSON.parse(JSON.stringify(this.listData))
const search= this.search
// 匹配關鍵字正則
const replaceReg = new RegExp(search, 'g')
// 高亮替換v-html值
const replaceString = `<font color='#F14F4A'>${search}</font>`
for (let i = 0; i < listData.length; i++) {
// 開始替換
listData [i]= listData [i].replace(replaceReg, replaceString)
}
return listData
}
_listData(){
return this.replaceArr.filter(data => {
return Object.keys(data).some(key => {
return (
String(data[key])
.toLowerCase()
.indexOf(this.search) > -1
)
})
})
},
//列表總高度
listHeight(){
return this._listData.length * this.itemSize;
},
//可顯示的列表項數
visibleCount(){
return Math.ceil(this.screenHeight / this.itemSize)
},
//偏移量對應的style
getTransform(){
return `translate3d(0,${this.startOffset}px,0)`;
},
//獲取真實顯示列表數據 + 模糊搜索
visibleData(){
return this._listData.slice(this.start, Math.min(this.end,this._listData.length));
}
},
watch: {
search() {
this.$refs.list.scrollTop = 0
}
},
現在就可以實現搜索這些數據了。