我們開發過程中經常會遇到這樣的需求,一個數據量很大的列表。
遇到,如果你有一個列表,
我們以百度信息流為例子
頁面打開,加載第一頁的數據,每次往下滾屏到接近底部,會加載下一屏,這樣也是保證獲取數據的http請求量是按需加載的。圖片懶加載,也能節省流量的資源。但是,隨著我們一直加載,產生的dom會越來越多。內存占用也會越來越高
還有我們常見的列表,很多情況下我們通過分頁來加載,但是有些情況下,比如不能使用分頁,這時候如果有千上萬的數據,就會產生大量的虛擬dom和真實dom,大量占用內存
如果說上面的列表我們還能通過分頁解決,那么Select選擇框,如果有成千上萬的選項,即使我們使用了滾動加載,滾動到最后也還是會產生大量的dom,我本地測試了1萬條選項的下拉框,卡頓顯現非常明顯,點擊后需要2s才能展開選項,并且我的筆記本風扇已經開始呼呼排風了。
這三類問題都有一個相似點,大量的并列的dom,但是這些dom都在一個可滾動的容器中,無論怎么滾動,只有一部分是可見的,
所以如果只有可見的數據才去創建虛擬dom,就會大量節省內存,并加快渲染速度。
我們以列表來作為例子,展示解決方案
<template>
<div class="list" style="height: 300px;overflow: scroll">
<div v-for="(item,index) in list" :key="index" class="item">
<div v-html="item.id"></div>
<div v-html="item.name"></div>
<div v-html="item.createTime"></div>
<div>
<Button>修改</Button>
<Button>刪除</Button>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
list: [
{
id: 1,
name: 'name' + 1,
createTime: '2018-09-09'
}, {
id: 2,
name: 'name' + 2,
createTime: '2018-09-09'
}, {
id: 3,
name: 'name' + 3,
createTime: '2018-09-09'
}, {
id: 3,
name: 'name' + 3,
createTime: '2018-09-09'
}
]
}
}
}
</script>
我們做一點改動,通過代碼生成一個10000條的list
<template>
<div class="list" style="height: 300px;overflow: scroll">
<div v-for="(item,index) in list" :key="index" class="item">
<div v-html="item.id"></div>
<div v-html="item.name"></div>
<div v-html="item.createTime"></div>
<div>
<Button>修改</Button>
<Button>刪除</Button>
</div>
</div>
</div>
</template>
<script>
export default {
created() {
for (let i = 0; i < 10000; i++) {
this.list.push({
id: 1,
name: 'name' + i,
createTime: '2018-09-09'
})
}
},
data() {
return {
list: []
}
}
}
</script>
根據這個想法設計的解決方案,思路有幾個關鍵點。
1、假滾動事件:拖動滾動滑塊,并不會影響左側的真實滾動,只是記錄滾動的位置。
2、根據滾動的位置,計算出對應的數據區間段,比如應該取340-350這10條。
3、根據滾動位置,和數據區間,把這幾條數據控制絕對定位,來模擬滾動效果。
第一步:假滾動事件
<template>
<div class="list" style="height: 300px;overflow: scroll">
<div :style="{height:list.length*40+'px'}"></div>
</div>
</template>
我們的每一行高度是40,那么我們創建一個高度時list.length*40的空元素,那么這個空元素的高度,跟展示所有的list的高度一樣,也就是說右側的滾動條效果跟原來的效果是一樣的。
第二步,根據滾動的位置,計算出對應的數據區間段
我們需要計算開始index和長度,然后截取list數據的一部分
<template>
<div class="list" style="height: 300px;overflow: scroll" ref="scrollDom" @scroll="scroll">
<div :style="{height:list.length*40+'px'}"></div>
</div>
</template>
<script>
export default {
created() {
for (let i = 0; i < 10000; i++) {
this.list.push({
id: 1,
name: 'name' + i,
createTime: '2018-09-09'
})
}
},
computed: {
limitCount() {
return 300 / 40;
}
},
data() {
return {
startIndex:0,
list: []
}
},
methods: {
scroll() {
// 根據滾動的距離,估算出這個滾動位置對應的數組序列,例如滾動100px,每條40px,對應第3條
let scrollTop = this.$refs.scrollDom.scrollTop;
this.startIndex = Math.floor(scrollTop / 40);
console.log(this.startIndex);
}
}
}
</script>
我們根據開始和結束需要,生成一個splitData,然后再把新生成的dom通過絕對位置調整。
完成的效果
我們可以看到,無論怎么滾動,效果跟全部加載是一樣的,但是通過右側的控制臺發現,dom數量只有固定的這幾個,但是每個dom內部的值在不停的修改。
完整的代碼
<template>
<div class="list" style="height: 300px;overflow: scroll" ref="scrollDom" @scroll="scroll">
<div :style="{height:list.length*40+'px'}"></div>
<div style="position:absolute;width: 100%" :style="{top:startIndex*40+'px'}">
<div v-for="(item,index) in splitData" :key="index" class="item">
<div v-html="item.id"></div>
<div v-html="item.name"></div>
<div v-html="item.createTime"></div>
<div>
<Button>修改</Button>
<Button>刪除</Button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
created() {
for (let i = 0; i < 10000; i++) {
this.list.push({
id: 1,
name: 'name' + i,
createTime: '2018-09-09'
})
}
},
computed: {
limitCount() {
return 300 / 40 + 2;
},
splitData() {
return this.list.slice(this.startIndex, this.startIndex + this.limitCount)
}
},
data() {
return {
startIndex: 0,
list: []
}
},
methods: {
scroll() {
// 根據滾動的距離,估算出這個滾動位置對應的數組序列,例如滾動100px,每條40px,對應第3條
let scrollTop = this.$refs.scrollDom.scrollTop;
this.startIndex = Math.floor(scrollTop / 40);
}
}
}
</script>
<style scoped lang="less">
.list {
border: solid 1px #5f5f5f;
background-color: white;
margin: 100px 0;
padding: 5px;
width: 500px;
position: relative;
.item {
display: flex;
height: 40px;
> * {
flex-grow: 1;
border: solid 1px #9e9e9e;
padding: 3px;
}
}
}
</style>