基于Vue實現(xiàn)封裝一個虛擬列表組件
正常情況下,我們對于數(shù)據(jù)都會分頁加載,最近項目中確實遇到了不能分頁的場景,如果不分頁,頁面渲染幾千條數(shù)據(jù)就會感知到卡頓,使用虛擬列表就勢在必行了,為了增加復(fù)用性,封裝成了組件。
組件效果

使用方法
<template>
<div>
<div class="virtual-list-md-wrap">
<hub-virtual-list :allData="data" itemHeight="70" :virtualData.sync="virtualData">
<div v-for="(item, index) in virtualData" class="item">
{{ item }}
<el-button type="primary" size="mini" plain @click="deleteItem(item)">刪除</el-button>
</div>
</hub-virtual-list>
</div>
</div>
</template>
<script>
export default {
data() {
return {
data: [],
virtualData: []
}
},
created() {
setTimeout(() => {
this.addData()
}, 1000)
},
watch: {
},
methods: {
addData() {
for(let i = 0; i <= 100000; i ++) {
this.$set(this.data, i, i)
}
},
deleteItem(index) {
this.data = this.data.filter((item) => item !== index)
}
}
}
</script>
<style>
.virtual-list-md-wrap {
height: 500px;
background-color: #FFFAF0;
}
.item {
border-bottom: 1px solid #666;
padding: 20px;
text-align: center;
}
</style>屬性
| 參數(shù) | 說明 | 類型 | 可選值 | 默認值 |
|---|---|---|---|---|
| allData | 全部數(shù)據(jù) | Array | - | [] |
| virtualData | 虛擬數(shù)據(jù) | Array | - | [] |
| itemHeight | 每行的高度,用于計算滾動距離 | Number, String | - | 30 |
插槽
| 插槽名 | 說明 |
|---|---|
| - | 自定義默認內(nèi)容,即主體區(qū)域 |
封裝過程
首先梳理我想要的組件效果:
- 滾動條正常顯示
- 加載渲染大量數(shù)據(jù)不卡頓
- 能對列表數(shù)據(jù)進行操作增刪等
滾動條正常顯示
需要把顯示框分為3部分:顯示高度,全部高度,虛擬數(shù)據(jù)高度
大概的比例是這樣的

為達到滾動條的效果,在最外層顯示高度設(shè)置overflow: auto可以把滾動條撐出來,全部高度則設(shè)置position: absolute;z-index: -1;height: auto;,虛擬數(shù)據(jù)高度則設(shè)置position: absolute; height: auto;
整體樣式代碼如下
<template>
<div class="hub-virtual-list">
<!-- 顯示高度 -->
<div ref="virtualList" class="hub-virtual-list-show-height" @scroll="scrollEvent($event)">
<!-- 全部高度,撐出滾動條 -->
<div class="hub-virtual-list-all-height" :style="{height: allHeight + 'px'}"/>
<!-- 存放顯示數(shù)據(jù) -->
<div class="virtual-list" :style="{ transform: getTransform }"/>
</div>
</div>
</template>
<style lang="scss" scoped>
.hub-virtual-list {
height: 100%;
&-show-height {
position: relative;
overflow: auto;
height: 100%;
-webkit-overflow-scrolling: touch;
}
&-all-height {
position: absolute;
left: 0;
top: 0;
right: 0;
z-index: -1;
height: auto;
}
.virtual-list {
position: absolute;
left: 0;
top: 0;
right: 0;
height: auto;
}
}
</style>
加載渲染大量數(shù)據(jù)不卡頓
如果想要渲染不卡頓,就得只加載顯示區(qū)域的虛擬數(shù)據(jù),虛擬數(shù)據(jù)的更新邏輯為:用startIndex和endIndex標志虛擬數(shù)據(jù)的起始索引和結(jié)束索引,在滾動條滑動時,通過計算滑動的距離去更新startIndex和endIndex。另外用offset標記偏移量,對虛擬數(shù)據(jù)區(qū)域設(shè)置transform: translate3d(0, ${this.offset}px, 0)跟著滾動條去移動
核心部分代碼如下
scrollEvent(e) {
const scrollTop = this.$refs.virtualList.scrollTop
// 起始索引 = 滾動距離 / 每項高度
this.startIndex = Math.floor(scrollTop / this.itemHeight)
// 結(jié)束索引 = 開始索引 + 可見數(shù)量
this.endIndex = this.startIndex + this.visibleCount
// 偏移量 = 滾動距離
this.offset = scrollTop - (scrollTop % this.itemHeight)
}能對列表數(shù)據(jù)進行操作增刪等
如果想要在數(shù)據(jù)里添加操作按鈕,則需要在封裝組件時設(shè)置插槽,且需要把虛擬數(shù)據(jù)同步給父組件
設(shè)置插槽
<!-- 顯示高度 -->
<div ref="virtualList" class="hub-virtual-list-show-height" @scroll="scrollEvent($event)">
<!-- 全部高度,撐出滾動條 -->
<div class="hub-virtual-list-all-height" :style="{height: allHeight + 'px'}"/>
<!-- 存放顯示數(shù)據(jù) -->
<div class="virtual-list" :style="{ transform: getTransform }">
<!-- 設(shè)置插槽 -->
<slot/>
</div>
</div>滾動時把虛擬數(shù)據(jù)同步給父組件
scrollEvent(e) {
const scrollTop = this.$refs.virtualList.scrollTop
// 起始索引 = 滾動距離 / 每項高度
this.startIndex = Math.floor(scrollTop / this.itemHeight)
// 結(jié)束索引 = 開始索引 + 可見數(shù)量
this.endIndex = this.startIndex + this.visibleCount
// 偏移量 = 滾動距離
this.offset = scrollTop - (scrollTop % this.itemHeight)
// 同步父組件數(shù)據(jù)
this.inVirtualData = this.allData.slice(this.startIndex, this.endIndex)
this.$emit('update:virtualData', this.inVirtualData)
}完整代碼
<template>
<div class="hub-virtual-list">
<!-- 顯示高度 -->
<div ref="virtualList" class="hub-virtual-list-show-height" @scroll="scrollEvent($event)">
<!-- 全部高度,撐出滾動條 -->
<div class="hub-virtual-list-all-height" :style="{height: allHeight + 'px'}"/>
<!-- 存放顯示數(shù)據(jù) -->
<div class="virtual-list" >
<slot/>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'hub-virtual-list',
props: {
// 全部數(shù)據(jù)
allData: {
type: Array,
default: () => []
},
// 虛擬數(shù)據(jù)
virtualData: {
type: Array,
default: () => []
},
// 每項高度
itemHeight: {
type: [Number, String],
default: '30'
},
// 每項樣式
itemStyle: {
type: Object,
default: () => {}
}
},
data() {
return {
// 起始索引
startIndex: 0,
// 結(jié)束索引
endIndex: null,
// 偏移量,計算滾動條
offset: 0,
inVirtualData: []
}
},
computed: {
// 所有高度
allHeight() {
// 每項高度 * 項數(shù)
return this.itemHeight * this.allData.length
},
// 可見數(shù)量
visibleCount() {
// 可見高度 / 每項高度
return Math.ceil(this.showHeight / this.itemHeight)
},
// 顯示數(shù)據(jù)的偏移量
getTransform() {
return `translate3d(0, ${this.offset}px, 0)`
}
},
watch: {
allData: {
handler() {
this.inVirtualData = this.allData.slice(this.startIndex, this.endIndex)
this.$emit('update:virtualData', this.inVirtualData)
},
deep: true
}
},
mounted() {
this.showHeight = this.$el.clientHeight
this.startIndex = 0
this.endIndex = this.startIndex + this.visibleCount
},
methods: {
scrollEvent(e) {
const scrollTop = this.$refs.virtualList.scrollTop
// 起始索引 = 滾動距離 / 每項高度
this.startIndex = Math.floor(scrollTop / this.itemHeight)
// 結(jié)束索引 = 開始索引 + 可見數(shù)量
this.endIndex = this.startIndex + this.visibleCount
// 偏移量 = 滾動距離
this.offset = scrollTop - (scrollTop % this.itemHeight)
// 同步父組件數(shù)據(jù)
this.inVirtualData = this.allData.slice(this.startIndex, this.endIndex)
this.$emit('update:virtualData', this.inVirtualData)
}
}
}
</script>
<style lang="scss" scoped>
.hub-virtual-list {
height: 100%;
&-show-height {
position: relative;
overflow: auto;
height: 100%;
-webkit-overflow-scrolling: touch;
}
&-all-height {
position: absolute;
left: 0;
top: 0;
right: 0;
z-index: -1;
height: auto;
}
.virtual-list {
position: absolute;
left: 0;
top: 0;
right: 0;
height: auto;
}
}
</style>待完善
使用組件時需要設(shè)置itemHeight屬性才能去計算整體的高度去保證滾動條的準確性,并且itemHeight的值要和實際高度相等,不然會出現(xiàn)滾動條滑到底部時出現(xiàn)空白的情況。同時組件僅支持每一行高度固定且相等的情況,對于每行數(shù)據(jù)高度不相等的情況目前還未完善。
以上就是基于Vue實現(xiàn)封裝一個虛擬列表組件的詳細內(nèi)容,更多關(guān)于Vue封裝虛擬列表組件的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue2文件流下載成功后文件格式錯誤、打不開及內(nèi)容缺失的解決方法
使用Vue時我們前端如何處理后端返回的文件流,下面這篇文章主要給大家介紹了關(guān)于vue2文件流下載成功后文件格式錯誤、打不開及內(nèi)容缺失的解決方法,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2023-04-04
elementUI中el-table表頭和內(nèi)容全部一行顯示完整的方法
最近參與web開發(fā)時,讓我解決一個elementui控制內(nèi)容單行顯示,下面這篇文章主要給大家介紹了關(guān)于elementUI中el-table表頭和內(nèi)容全部一行顯示完整的方法,需要的朋友可以參考下2023-06-06
VUE中的export default和export使用方法解析
export default和export都能導(dǎo)出一個模塊里面的常量,函數(shù),文件,模塊等,在其它文件或模塊中通過import來導(dǎo)入常量,函數(shù),文件或模塊。但是,在一個文件或模塊中export,import可以有多個,export default卻只能有一個。2022-12-12
vue 實現(xiàn)左右拖拽元素并且不超過他的父元素的寬度
這篇文章主要介紹了vue 實現(xiàn)左右拖拽元素并且不超過他的父元素的寬度,需要的朋友可以參考下2018-11-11
Vue項目中使用WebUploader實現(xiàn)文件上傳的方法
WebUploader是由 Baidu WebFE(FEX) 團隊開發(fā)的一個簡單的以 HTML5為主 , FLASH為輔 的現(xiàn)代 文件上傳組件 。這篇文章主要介紹了在Vue項目中使用WebUploader實現(xiàn)文件上傳,需要的朋友可以參考下2019-07-07

