基于Vue实现封装一个虚拟列表组件
正常情况下,我们对于数据都会分页加载,最近项目中确实遇到了不能分页的场景,如果不分页,页面渲染几千条数据就会感知到卡顿,使用虚拟列表就势在必行了,为了增加复用性,封装成了组件。
组件效果
使用方法
<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封装虚拟列表组件的资料请关注脚本之家其它相关文章!
相关文章
vue2文件流下载成功后文件格式错误、打不开及内容缺失的解决方法
使用Vue时我们前端如何处理后端返回的文件流,下面这篇文章主要给大家介绍了关于vue2文件流下载成功后文件格式错误、打不开及内容缺失的解决方法,文中通过实例代码介绍的非常详细,需要的朋友可以参考下2023-04-04elementUI中el-table表头和内容全部一行显示完整的方法
最近参与web开发时,让我解决一个elementui控制内容单行显示,下面这篇文章主要给大家介绍了关于elementUI中el-table表头和内容全部一行显示完整的方法,需要的朋友可以参考下2023-06-06VUE中的export default和export使用方法解析
export default和export都能导出一个模块里面的常量,函数,文件,模块等,在其它文件或模块中通过import来导入常量,函数,文件或模块。但是,在一个文件或模块中export,import可以有多个,export default却只能有一个。2022-12-12
最新评论