不同場景下Vue中虛擬列表實現
虛擬列表用來解決大數據量數據渲染問題,由于一次性渲染性能低,所以誕生了虛擬列表渲染。該場景下可視區(qū)高度都是相對固定的。相對固定是指在一定條件下可以被改變。
虛擬列表的要做的事是確保性能的前提下,利用一定的技術模擬全數據一次性渲染后效果。
主要通過兩件事:
1,渲染數據,適時變更渲染數據
2,模擬滾動效果
場景一、可視區(qū)高度固定,單條數據高度固定且相同
比如element-ui el-select下拉選擇框,這是最簡單的場景。相關參數固定,也最好實現。
<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: { // 所有列表數據 listData: { type: Array, default: () => [] }, // 每項高度 itemSize: { type: Number, default: 50 } }, 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 { // 可視區(qū)域高度 screenHeight: 0, // 偏移量 startOffset: 0, // 起始索引 start: 0, // 結束索引 end: null } }, methods: { scrollEvent () { // 當前滾動位置 const 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> .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>
初始化渲染數據
單條數據高度確定,可視區(qū)高度確定??梢杂枚哂嬎愠鲲@示條數。初始索引為0,結束索引為顯示條數。之后截取總數據當中對應的數據即可。
// 可顯示的列表項數 visibleCount () { return Math.ceil(this.screenHeight / this.itemSize) }, ... mounted () { // 開始索引 this.start = 0 // 結束索引 this.end = this.start + this.visibleCount },
滾動邏輯
可視區(qū)高度確定,列表需要模擬出滾動條,這里采用占位div撐開方案。隨著滾動條滾動,監(jiān)聽滾動高度計算出開始索引和結束索引,再計算出滾動偏移量,再利用translate3d滾動,translate3d且因為沒有重排重繪,所以性能更好。
methods: { scrollEvent () { // 當前滾動位置 const scrollTop = this.$refs.list.scrollTop // 此時的開始索引 this.start = Math.floor(scrollTop / this.itemSize) // 此時的結束索引 this.end = this.start + this.visibleCount // 此時的偏移量 this.startOffset = scrollTop - (scrollTop % this.itemSize) } }
再渲染邏輯
隨著滾動條滾動,監(jiān)聽滾動高度計算出開始索引和結束索引,重置開始和結束索引,也就自然引發(fā)Vue重新渲染。
場景二、可視區(qū)高度固定,單條數據高度確定但不相同
如果單條數據不固定,一定是因為有不同的數據展示方式,每種方式可以封裝成組件,之后動態(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>
整體實現
<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: { // 所有列表數據 listData: { type: Array, default: () => [] } }, computed: { // 列表總高度 listHeight () { return this.listData.reduce((acc, curVal) => { return acc + curVal.height }, 0) }, // 可顯示的列表項數 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 }, // 偏移量對應的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.end = this.start + this.visibleCount }, data () { return { // 可視區(qū)域高度 screenHeight: 0, // 偏移量 startOffset: 0, // 起始索引 start: 0, // 結束索引 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) // 此時的結束索引 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>
初始化渲染邏輯
通過累加組件高度的方式算出初始化顯示條數,初始化開始索引為0,結束索引為顯示條數
// 可顯示的列表項數 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)聽滾動高度,再通過累加方式算出最新的開始索引和結束索引,算出滾動偏移量
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) // 此時的結束索引 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)聽滾動高度計算出開始索引和結束索引,重置開始和結束索引,也就自然引發(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>
場景三、以上兩種情況追加數據
以上兩種場景。如何追加數據呢?
scrollEvent () { // 當前滾動位置 const scrollTop = this.$refs.list.scrollTop // 此時的開始索引 this.start = this.getStart(scrollTop) // 此時的結束索引 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 // 追加數據 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ā)生變化時,渲染數據發(fā)生變化了,但渲染數據的高度不是連續(xù)的,所以需要動態(tài)的設置偏移量。當滾動時起始索引不發(fā)生變化時,此時可以什么也不做,滾動顯示的內容由瀏覽器控制。
以上就是不同場景下Vue中虛擬列表實現的詳細內容,更多關于vue虛擬列表的資料請關注腳本之家其它相關文章!
相關文章
關于el-form表單驗證中的validator與validate使用時的問題
這篇文章主要介紹了關于el-form表單驗證中的validator與validate使用時的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-06-06vue2.0使用v-for循環(huán)制作多級嵌套菜單欄
這篇文章主要介紹了vue2.0制作多級嵌套菜單欄,主要使用v-for循環(huán)生成一個多級嵌套菜單欄,這個方法應用非常廣泛,需要的朋友可以參考下2018-06-06淺談vue中改elementUI默認樣式引發(fā)的static與assets的區(qū)別
下面小編就為大家分享一篇淺談vue中改elementUI默認樣式引發(fā)的static 與assets的區(qū)別,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-02-02