正常情况下,我们对于数据都会分页加载,最近项目中确实遇到了不能分页的场景,如果不分页,页面渲染几千条数据就会感知到卡顿,使用虚拟列表就势在必行了,为了增加复用性,封装成了组件。
组件效果
使用方法
<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>
属性
参数 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
allData | 全部数据 | Array | - | [] |
virtualData | 虚拟数据 | Array | - | [] |
itemHeight | 每行的高度,用于计算滚动距离 | Number, String | - | 30 |
插槽
插槽名 | 说明 |
---|---|
- | 自定义默认内容,即主体区域 |
封装过程
首先梳理我想要的组件效果:
- 滚动条正常显示
- 加载渲染大量数据不卡顿
- 能对列表数据进行操作增删等
滚动条正常显示
需要把显示框分为3部分:显示高度,全部高度,虚拟数据高度
大概的比例是这样的
为达到滚动条的效果,在最外层显示高度设置overflow: auto
可以把滚动条撑出来,全部高度则设置position: absolute;z-index: -1;height: auto;
,虚拟数据高度则设置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'}"/>
<!-- 存放显示数据 -->
<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>
加载渲染大量数据不卡顿
如果想要渲染不卡顿,就得只加载显示区域的虚拟数据,虚拟数据的更新逻辑为:用startIndex
和endIndex
标志虚拟数据的起始索引和结束索引,在滚动条滑动时,通过计算滑动的距离去更新startIndex
和endIndex
。另外用offset
标记偏移量,对虚拟数据区域设置transform: translate3d(0, ${this.offset}px, 0)
跟着滚动条去移动
核心部分代码如下
scrollEvent(e) {
const scrollTop = this.$refs.virtualList.scrollTop
// 起始索引 = 滚动距离 / 每项高度
this.startIndex = Math.floor(scrollTop / this.itemHeight)
// 结束索引 = 开始索引 + 可见数量
this.endIndex = this.startIndex + this.visibleCount
// 偏移量 = 滚动距离
this.offset = scrollTop - (scrollTop % this.itemHeight)
}
能对列表数据进行操作增删等
如果想要在数据里添加操作按钮,则需要在封装组件时设置插槽,且需要把虚拟数据同步给父组件
设置插槽
<!-- 显示高度 -->
<div ref="virtualList" class="hub-virtual-list-show-height" @scroll="scrollEvent($event)">
<!-- 全部高度,撑出滚动条 -->
<div class="hub-virtual-list-all-height" :style="{height: allHeight + 'px'}"/>
<!-- 存放显示数据 -->
<div class="virtual-list" :style="{ transform: getTransform }">
<!-- 设置插槽 -->
<slot/>
</div>
</div>
滚动时把虚拟数据同步给父组件
scrollEvent(e) {
const scrollTop = this.$refs.virtualList.scrollTop
// 起始索引 = 滚动距离 / 每项高度
this.startIndex = Math.floor(scrollTop / this.itemHeight)
// 结束索引 = 开始索引 + 可见数量
this.endIndex = this.startIndex + this.visibleCount
// 偏移量 = 滚动距离
this.offset = scrollTop - (scrollTop % this.itemHeight)
// 同步父组件数据
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'}"/>
<!-- 存放显示数据 -->
<div class="virtual-list" >
<slot/>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'hub-virtual-list',
props: {
// 全部数据
allData: {
type: Array,
default: () => []
},
// 虚拟数据
virtualData: {
type: Array,
default: () => []
},
// 每项高度
itemHeight: {
type: [Number, String],
default: '30'
},
// 每项样式
itemStyle: {
type: Object,
default: () => {}
}
},
data() {
return {
// 起始索引
startIndex: 0,
// 结束索引
endIndex: null,
// 偏移量,计算滚动条
offset: 0,
inVirtualData: []
}
},
computed: {
// 所有高度
allHeight() {
// 每项高度 * 项数
return this.itemHeight * this.allData.length
},
// 可见数量
visibleCount() {
// 可见高度 / 每项高度
return Math.ceil(this.showHeight / this.itemHeight)
},
// 显示数据的偏移量
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)
// 结束索引 = 开始索引 + 可见数量
this.endIndex = this.startIndex + this.visibleCount
// 偏移量 = 滚动距离
this.offset = scrollTop - (scrollTop % this.itemHeight)
// 同步父组件数据
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>
待完善
使用组件时需要设置itemHeight
属性才能去计算整体的高度去保证滚动条的准确性,并且itemHeight的值要和实际高度相等,不然会出现滚动条滑到底部时出现空白的情况。同时组件仅支持每一行高度固定且相等的情况,对于每行数据高度不相等的情况目前还未完善。
以上就是基于Vue实现封装一个虚拟列表组件的详细内容,更多关于Vue封装虚拟列表组件的资料请关注编程网其它相关文章!