不同場景下Vue中虛擬列表實現(xiàn)
虛擬列表用來解決大數(shù)據(jù)量數(shù)據(jù)渲染問題,由于一次性渲染性能低,所以誕生了虛擬列表渲染。該場景下可視區(qū)高度都是相對固定的。相對固定是指在一定條件下可以被改變。
虛擬列表的要做的事是確保性能的前提下,利用一定的技術(shù)模擬全數(shù)據(jù)一次性渲染后效果。
主要通過兩件事:
1,渲染數(shù)據(jù),適時變更渲染數(shù)據(jù)
2,模擬滾動效果
場景一、可視區(qū)高度固定,單條數(shù)據(jù)高度固定且相同
比如element-ui el-select下拉選擇框,這是最簡單的場景。相關(guān)參數(shù)固定,也最好實現(xiàn)。
<template>
<div ref="list" class="render-list-container" @scroll="scrollEvent($event)">
<div class="render-list-phantom" :style="{ height: listHeight + 'px' }"></div>
<div class="render-list" :style="{ transform: getTransform }">
<template
v-for="item in visibleData"
>
<slot :value="item.value" :height="item.height + 'px'" :index="item.id"></slot>
</template>
</div>
</div>
</template>
<script>
export default {
name: 'VirtualList',
props: {
// 所有列表數(shù)據(jù)
listData: {
type: Array,
default: () => []
},
// 每項高度
itemSize: {
type: Number,
default: 50
}
},
computed: {
// 列表總高度
listHeight () {
return this.listData.length * this.itemSize
},
// 可顯示的列表項數(shù)
visibleCount () {
return Math.ceil(this.screenHeight / this.itemSize)
},
// 偏移量對應(yīng)的style
getTransform () {
return `translate3d(0,${this.startOffset}px,0)`
},
// 獲取真實顯示列表數(shù)據(jù)
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 {
// 可視區(qū)域高度
screenHeight: 0,
// 偏移量
startOffset: 0,
// 起始索引
start: 0,
// 結(jié)束索引
end: null
}
},
methods: {
scrollEvent () {
// 當前滾動位置
const scrollTop = this.$refs.list.scrollTop
// 此時的開始索引
this.start = Math.floor(scrollTop / this.itemSize)
// 此時的結(jié)束索引
this.end = this.start + this.visibleCount
// 此時的偏移量
this.startOffset = scrollTop - (scrollTop % this.itemSize)
}
}
}
</script>
<style scoped>
.render-list-container {
overflow: auto;
position: relative;
-webkit-overflow-scrolling: touch;
height: 200px;
}
.render-list-phantom {
position: absolute;
left: 0;
right: 0;
z-index: -1;
}
.render-list {
text-align: center;
}
</style>
初始化渲染數(shù)據(jù)
單條數(shù)據(jù)高度確定,可視區(qū)高度確定??梢杂枚哂嬎愠鲲@示條數(shù)。初始索引為0,結(jié)束索引為顯示條數(shù)。之后截取總數(shù)據(jù)當中對應(yīng)的數(shù)據(jù)即可。
// 可顯示的列表項數(shù)
visibleCount () {
return Math.ceil(this.screenHeight / this.itemSize)
},
...
mounted () {
// 開始索引
this.start = 0
// 結(jié)束索引
this.end = this.start + this.visibleCount
},
滾動邏輯
可視區(qū)高度確定,列表需要模擬出滾動條,這里采用占位div撐開方案。隨著滾動條滾動,監(jiān)聽滾動高度計算出開始索引和結(jié)束索引,再計算出滾動偏移量,再利用translate3d滾動,translate3d且因為沒有重排重繪,所以性能更好。
methods: {
scrollEvent () {
// 當前滾動位置
const scrollTop = this.$refs.list.scrollTop
// 此時的開始索引
this.start = Math.floor(scrollTop / this.itemSize)
// 此時的結(jié)束索引
this.end = this.start + this.visibleCount
// 此時的偏移量
this.startOffset = scrollTop - (scrollTop % this.itemSize)
}
}
再渲染邏輯
隨著滾動條滾動,監(jiān)聽滾動高度計算出開始索引和結(jié)束索引,重置開始和結(jié)束索引,也就自然引發(fā)Vue重新渲染。
場景二、可視區(qū)高度固定,單條數(shù)據(jù)高度確定但不相同
如果單條數(shù)據(jù)不固定,一定是因為有不同的數(shù)據(jù)展示方式,每種方式可以封裝成組件,之后動態(tài)展示。這塊封裝了三個組件,高度分別為20px、30px、50px。
封裝組件
<template>
<div style="height:20px;border:1px solid #333;">
height:20px;---{{ index }}
</div>
</template>
<script>
export default {
props: ['index']
}
</script>
<template>
<div style="height:30px;border:1px solid #333;">
height: 30px---{{ index }}
</div>
</template>
<script>
export default {
props: ['index']
}
</script>
<template>
<div style="height:50px;border:1px solid #333;">
height: 50px--{{ index }}
</div>
</template>
<script>
export default {
props: ['index']
}
</script>
整體實現(xiàn)
<template>
<div ref="list" class="render-list-container" @scroll="scrollEvent($event)">
<div
class="render-list-phantom"
:style="{ height: listHeight + 'px' }"
></div>
<div class="render-list" :style="{ transform: getTransform }">
<template v-for="item in visibleData">
<slot :type="item.type" :index="item.id"></slot>
</template>
</div>
</div>
</template>
<script>
export default {
name: 'VirtualList',
props: {
// 所有列表數(shù)據(jù)
listData: {
type: Array,
default: () => []
}
},
computed: {
// 列表總高度
listHeight () {
return this.listData.reduce((acc, curVal) => {
return acc + curVal.height
}, 0)
},
// 可顯示的列表項數(shù)
visibleCount () {
let accHeight = 0
let count = 0
for (let i = 0; i < this.listData.length; i++) {
accHeight += this.listData[i].height
if (accHeight >= this.screenHeight) {
count++
break
}
count++
}
return count
},
// 偏移量對應(yīng)的style
getTransform () {
return `translate3d(0,${this.startOffset}px,0)`
},
// 獲取真實顯示列表數(shù)據(jù)
visibleData () {
return this.listData.slice(
this.start,
Math.min(this.end, this.listData.length)
)
}
},
mounted () {
this.screenHeight = this.$el.clientHeight
this.end = this.start + this.visibleCount
},
data () {
return {
// 可視區(qū)域高度
screenHeight: 0,
// 偏移量
startOffset: 0,
// 起始索引
start: 0,
// 結(jié)束索引
end: null
}
},
methods: {
getStart (scrollTop) {
var height = 0
var start = 0
var i = 0
while (true) {
const currentItem = this.listData[i].height
if (currentItem) {
height += currentItem
if (height >= scrollTop) {
start = i
break
}
} else {
break
}
i++
}
return start
},
scrollEvent () {
// 當前滾動位置
const scrollTop = this.$refs.list.scrollTop
// 此時的開始索引
this.start = this.getStart(scrollTop)
// 此時的結(jié)束索引
this.end = this.start + this.visibleCount
const offsetHeight = scrollTop - (this.visibleData.reduce((acc, curVal) => acc + curVal.height, 0) - this.screenHeight)
// 此時的偏移量
this.startOffset = offsetHeight < 0 ? 0 : offsetHeight
}
}
}
</script>
<style scoped>
.render-list-container {
overflow: auto;
position: relative;
-webkit-overflow-scrolling: touch;
height: 200px;
}
.render-list-phantom {
position: absolute;
left: 0;
right: 0;
z-index: -1;
}
.render-list {
text-align: center;
}
</style>
初始化渲染邏輯
通過累加組件高度的方式算出初始化顯示條數(shù),初始化開始索引為0,結(jié)束索引為顯示條數(shù)
// 可顯示的列表項數(shù)
visibleCount () {
let accHeight = 0
let count = 0
for (let i = 0; i < this.listData.length; i++) {
accHeight += this.listData[i].height
if (accHeight >= this.screenHeight) {
count++
break
}
count++
}
return count
},
滾動邏輯
依然采用占位div撐開方案保證可以滾動。隨著滾動條滾動,監(jiān)聽滾動高度,再通過累加方式算出最新的開始索引和結(jié)束索引,算出滾動偏移量
methods: {
getStart (scrollTop) {
var height = 0
var i = 0
while (true) {
const currentItem = this.listData[i].height
height += currentItem
if (height >= scrollTop) {
start = ++i
break
}
i++
}
return i
},
scrollEvent () {
// 當前滾動位置
const scrollTop = this.$refs.list.scrollTop
// 此時的開始索引
this.start = this.getStart(scrollTop)
// 此時的結(jié)束索引
this.end = this.start + this.visibleCount
const offsetHeight = scrollTop - (this.visibleData.reduce((acc, curVal) => acc + curVal.height, 0) - this.screenHeight)
// 此時的偏移量
this.startOffset = offsetHeight < 0 ? 0 : offsetHeight
}
}
再渲染邏輯
監(jiān)聽滾動高度計算出開始索引和結(jié)束索引,重置開始和結(jié)束索引,也就自然引發(fā)Vue重新渲染。
測試
<template>
<div class="render-show">
<div>
<VirtualList :listData="data">
<template slot-scope="{type, index}">
<component :is="type" :index="index"></component>
</template>
</VirtualList>
</div>
</div>
</template>
<script>
import VirtualList from './parts/VirtualList'
import Height20 from './parts/Height20'
import Height30 from './parts/Height30'
import Height50 from './parts/Height50'
const d = []
for (let i = 0; i < 1000; i++) {
const type = i % 3 === 0 ? i % 2 === 0 ? 'Height30' : 'Height50' : 'Height20'
d.push({ id: i, value: i, type: type, height: type === 'Height30' ? 30 : type === 'Height20' ? 20 : 50 })
}
export default {
name: 'VirtualList-test',
data () {
return {
data: d
}
},
components: {
VirtualList,
Height20,
Height30,
Height50
}
}
</script>
<style>
.render-show {
display: flex;
justify-content: center;
}
.render-show > div{
width:500px;
margin-top:40px;
}
.render-list-item {
color: #555;
box-sizing: border-box;
border-bottom: 1px solid #999;
box-sizing: border-box;
}
</style>

場景三、以上兩種情況追加數(shù)據(jù)
以上兩種場景。如何追加數(shù)據(jù)呢?
scrollEvent () {
// 當前滾動位置
const scrollTop = this.$refs.list.scrollTop
// 此時的開始索引
this.start = this.getStart(scrollTop)
// 此時的結(jié)束索引
this.end = this.start + this.visibleCount
const offsetHeight = scrollTop - (this.visibleData.reduce((acc, curVal) => acc + curVal.height, 0) - this.screenHeight)
// 此時的偏移量
this.startOffset = offsetHeight < 0 ? 0 : offsetHeight
// 追加數(shù)據(jù)
if (this.end + 1 >= this.listData.length) {
this.$emit('appendData', this.listData.length)
}
}
...
appendData (start) {
const d = []
for (let i = start; i < start + 10; i++) {
const type = i % 3 === 0 ? i % 2 === 0 ? 'Height30' : 'Height50' : 'Height20'
d.push({ id: i, value: i, type: type, height: type === 'Height30' ? 30 : type === 'Height20' ? 20 : 50 })
}
this.data = [...this.data, ...d]
}
注意事項
以上兩個場景中均對偏移量做了處理
this.startOffset = scrollTop - (scrollTop % this.itemSize)
const offsetHeight = scrollTop - (this.visibleData.reduce((acc, curVal) => acc + curVal.height, 0) - this.screenHeight)
// 此時的偏移量
this.startOffset = offsetHeight < 0 ? 0 : offsetHeight
真實的滾動就是滾動條滾動了多少,可視區(qū)就向上移動多少。但虛擬滾動不是的。當起始索引發(fā)生變化時,渲染數(shù)據(jù)發(fā)生變化了,但渲染數(shù)據(jù)的高度不是連續(xù)的,所以需要動態(tài)的設(shè)置偏移量。當滾動時起始索引不發(fā)生變化時,此時可以什么也不做,滾動顯示的內(nèi)容由瀏覽器控制。
以上就是不同場景下Vue中虛擬列表實現(xiàn)的詳細內(nèi)容,更多關(guān)于vue虛擬列表的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
關(guān)于el-form表單驗證中的validator與validate使用時的問題
這篇文章主要介紹了關(guān)于el-form表單驗證中的validator與validate使用時的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-06-06
vue2.0使用v-for循環(huán)制作多級嵌套菜單欄
這篇文章主要介紹了vue2.0制作多級嵌套菜單欄,主要使用v-for循環(huán)生成一個多級嵌套菜單欄,這個方法應(yīng)用非常廣泛,需要的朋友可以參考下2018-06-06
淺談vue中改elementUI默認樣式引發(fā)的static與assets的區(qū)別
下面小編就為大家分享一篇淺談vue中改elementUI默認樣式引發(fā)的static 與assets的區(qū)別,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-02-02

