目錄
一.問題背景
二.代碼講解
三.代碼改裝
四.代碼發布
今天我們來學習如何手寫一個虛擬列表,本文將把虛擬列表進行拆分并講解,然后發布到npm網站上.
一.問題背景
為什么需要虛擬列表呢?這是因為在面對大量數據的時候,我們的瀏覽器會將所有數據都渲染到表格上面,但是渲染極其消耗時間,就會出現瀏覽器卡頓的現象.總的來說就是機器性能不行,需要前端對體驗進行優化.
同時呢,我們其實正常人眼睛能看清的程度下,一個屏幕也就20-50行數據,面對10^4以上的數據的時候,如果為了只看這么點數據,而將所有數據都直接渲染,這會在短時間消耗大量的算力.
我們前端對同樣數據量的數據獲取和數據渲染是兩個過程,其中渲染的速度遠慢于數據獲取.所以我們采用虛擬列表這個技巧,每次計算視口可以容納幾個元素,讓后將這些元素從總列表當中計算出來,只渲染這部分可視數據,就將壓力分散到各段時間,很大程度上可以降低性能壓力.
二.代碼講解
我最開始看的面經,作者是用的Vue2寫的(不過我忘記出處了),代碼如下:
<!-- /component/HelloWorld.vue --><template><div ref="list" class="infinite-list-container" @scroll="scrollEvent($event)"><divclass="infinite-list-phantom":style="{ height: listHeight + 'px' }"></div><div class="infinite-list" :style="{ transform: getTransform }"><divref="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: "TheVirtualList",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);},//偏移量對應的stylegetTransform() {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;console.log('查看高度',this.screenHeight);this.start = 0;this.end = this.start + this.visibleCount;// console.log(`查看傳入組件參數:,${this.itemSize}`);},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);console.log('查看滾動位置:',scrollTop);},},
};
</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>
這是一段Vue2代碼,我來解釋下其中的特殊之處
this.$el.clientHeight;
這段代碼,代表獲取當前組件的高度
this.$refs.list.scrollTop;
這串代碼,代表獲取ref值為list的容器的右側滑動條距離頂部有多少px
這里面用到的技巧就是,這個虛擬表格看起來是一個高度很高的容器,包裹著一個填滿了數據的子容器,滑不完
實際上該組件是一個高度很高的空容器+一個只顯示一個屏幕數據量的容器+絕對定位進行布局
實際效果如下:
三.代碼改裝
由于現在都在寫Vue,切是組合式,雖然框架兼容vue2寫法,但是還是習慣Vue3組合式的寫法
不同的是,因為我們不能再用this.$el.clientHeight;來獲取該組件的高度了,所以我在組件最外層又套了一個div,并打了一個ref,用其的高度來進行代替.
代碼如下:
<template><div ref="virtualList" style="height: 100%;"><div ref="list" class="infinite-list-container" @scroll="handleScroll()"><divclass="infinite-list-phantom":style="{ height: `${listHeight}px` }"></div><div class="infinite-list" :style="{ transform: getTransform }"><divref="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></div>
</template><script setup>
import { onMounted } from 'vue';
import { computed } from 'vue';
import { defineProps ,ref} from 'vue';const props = defineProps({//所有列表數據listData: {type: Array,default: () => [],},//每項高度itemSize: {type: Number,default: 200,},
})
const virtualList = ref(null);
const list = ref(null)
// 虛擬列表的數據結構
const virtualListInfo = ref({start: 0,end: null,startOffset: 0,
})
// 虛擬底板高度
const listHeight = computed(() => {return props.listData.length * props.itemSize
})
// 獲取偏移
const getTransform = computed(() => {return `translate3d(0,${virtualListInfo.value.startOffset}px,0)`;
})
// 一頁渲染的元素個數
const visibleCount = computed(() => {return Math.ceil(virtualListInfo.value.screenHeight / props.itemSize);
})
// 要渲染的列表
const visibleData = computed(() => {// slice特性,如果第二個參數超過數組長度,那么直接獲取到末尾即可return props.listData.slice(virtualListInfo.value.start,virtualListInfo.value.end)
})
const aaa = ref(1)
console.log('xxxxx',props,aaa);const handleScroll = ()=> {if (list.value) {let scrollTop = list.value.scrollTop//此時的開始索引virtualListInfo.value.start = Math.floor(scrollTop / props.itemSize);//此時的結束索引virtualListInfo.value.end = virtualListInfo.value.start + visibleCount.value;//此時的偏移量virtualListInfo.value.startOffset = scrollTop - (scrollTop % props.itemSize);console.log('查看滾動位置:',scrollTop);} else {console.log('list未引用');}
}
onMounted(() => {virtualListInfo.value.screenHeight = virtualList.value.clientHeight;console.log('查看高度',virtualListInfo.value.screenHeight);virtualListInfo.value.start = 0;virtualListInfo.value.end = virtualListInfo.value.start + visibleCount.value;// console.log(`查看傳入組件參數:,${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>
四.代碼發布
我們封裝好了這些組件,但是想要其他人發布,所以我們就需要將其放到公網上.像我們開發項目需要從倉庫下載別人的包一樣,我們也可以發布自己的包到npm倉庫上
你可以參考:如何發布自己的npm包(超詳細步驟,博主都在用)_npm 發布-CSDN博客
我將上面的代碼發布到我自己的倉庫了,名稱為:lqd-raw-component
歡迎下載嘗試喵!