我們開發(fā)過程中經(jīng)常會(huì)遇到這樣的需求躺坟,一個(gè)數(shù)據(jù)量很大的列表。
遇到乳蓄,如果你有一個(gè)列表咪橙,
我們以百度信息流為例子
頁面打開,加載第一頁的數(shù)據(jù)虚倒,每次往下滾屏到接近底部美侦,會(huì)加載下一屏,這樣也是保證獲取數(shù)據(jù)的http請(qǐng)求量是按需加載的魂奥。圖片懶加載菠剩,也能節(jié)省流量的資源。但是捧弃,隨著我們一直加載,產(chǎn)生的dom會(huì)越來越多擦囊。內(nèi)存占用也會(huì)越來越高
還有我們常見的列表违霞,很多情況下我們通過分頁來加載,但是有些情況下瞬场,比如不能使用分頁买鸽,這時(shí)候如果有千上萬的數(shù)據(jù),就會(huì)產(chǎn)生大量的虛擬dom和真實(shí)dom贯被,大量占用內(nèi)存
如果說上面的列表我們還能通過分頁解決眼五,那么Select選擇框妆艘,如果有成千上萬的選項(xiàng),即使我們使用了滾動(dòng)加載看幼,滾動(dòng)到最后也還是會(huì)產(chǎn)生大量的dom批旺,我本地測(cè)試了1萬條選項(xiàng)的下拉框,卡頓顯現(xiàn)非常明顯诵姜,點(diǎn)擊后需要2s才能展開選項(xiàng)汽煮,并且我的筆記本風(fēng)扇已經(jīng)開始呼呼排風(fēng)了。
這三類問題都有一個(gè)相似點(diǎn)棚唆,大量的并列的dom暇赤,但是這些dom都在一個(gè)可滾動(dòng)的容器中,無論怎么滾動(dòng)宵凌,只有一部分是可見的鞋囊,
所以如果只有可見的數(shù)據(jù)才去創(chuàng)建虛擬dom,就會(huì)大量節(jié)省內(nèi)存瞎惫,并加快渲染速度溜腐。
我們以列表來作為例子,展示解決方案
<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>
我們做一點(diǎn)改動(dòng)微饥,通過代碼生成一個(gè)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>
根據(jù)這個(gè)想法設(shè)計(jì)的解決方案逗扒,思路有幾個(gè)關(guān)鍵點(diǎn)。
1欠橘、假滾動(dòng)事件:拖動(dòng)滾動(dòng)滑塊矩肩,并不會(huì)影響左側(cè)的真實(shí)滾動(dòng),只是記錄滾動(dòng)的位置肃续。
2黍檩、根據(jù)滾動(dòng)的位置,計(jì)算出對(duì)應(yīng)的數(shù)據(jù)區(qū)間段始锚,比如應(yīng)該取340-350這10條刽酱。
3、根據(jù)滾動(dòng)位置瞧捌,和數(shù)據(jù)區(qū)間棵里,把這幾條數(shù)據(jù)控制絕對(duì)定位,來模擬滾動(dòng)效果姐呐。
第一步:假滾動(dòng)事件
<template>
<div class="list" style="height: 300px;overflow: scroll">
<div :style="{height:list.length*40+'px'}"></div>
</div>
</template>
我們的每一行高度是40殿怜,那么我們創(chuàng)建一個(gè)高度時(shí)list.length*40的空元素,那么這個(gè)空元素的高度曙砂,跟展示所有的list的高度一樣头谜,也就是說右側(cè)的滾動(dòng)條效果跟原來的效果是一樣的。
第二步鸠澈,根據(jù)滾動(dòng)的位置柱告,計(jì)算出對(duì)應(yīng)的數(shù)據(jù)區(qū)間段
我們需要計(jì)算開始index和長度截驮,然后截取list數(shù)據(jù)的一部分
<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() {
// 根據(jù)滾動(dòng)的距離,估算出這個(gè)滾動(dòng)位置對(duì)應(yīng)的數(shù)組序列际度,例如滾動(dòng)100px葵袭,每條40px,對(duì)應(yīng)第3條
let scrollTop = this.$refs.scrollDom.scrollTop;
this.startIndex = Math.floor(scrollTop / 40);
console.log(this.startIndex);
}
}
}
</script>
我們根據(jù)開始和結(jié)束需要甲脏,生成一個(gè)splitData眶熬,然后再把新生成的dom通過絕對(duì)位置調(diào)整。
完成的效果
我們可以看到块请,無論怎么滾動(dòng)娜氏,效果跟全部加載是一樣的,但是通過右側(cè)的控制臺(tái)發(fā)現(xiàn)墩新,dom數(shù)量只有固定的這幾個(gè)贸弥,但是每個(gè)dom內(nèi)部的值在不停的修改。
完整的代碼
<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() {
// 根據(jù)滾動(dòng)的距離海渊,估算出這個(gè)滾動(dòng)位置對(duì)應(yīng)的數(shù)組序列绵疲,例如滾動(dòng)100px,每條40px臣疑,對(duì)應(yīng)第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>