基于Vue实现封装一个虚拟列表组件

 更新时间:2023年03月07日 10:00:13   作者:前端小小梦  
正常情况下,我们对于数据都会分页加载,最近项目中确实遇到了不能分页的场景,如果不分页,页面渲染几千条数据就会感知到卡顿,使用虚拟列表就势在必行了。本文主要介绍了如何基于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>

加载渲染大量数据不卡顿

如果想要渲染不卡顿,就得只加载显示区域的虚拟数据,虚拟数据的更新逻辑为:用startIndexendIndex标志虚拟数据的起始索引和结束索引,在滚动条滑动时,通过计算滑动的距离去更新startIndexendIndex。另外用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封装虚拟列表组件的资料请关注脚本之家其它相关文章!

相关文章

  • 使用Plotly.js在Vue中创建交互式散点图的示例代码

    使用Plotly.js在Vue中创建交互式散点图的示例代码

    Plotly.js是一个功能强大的JavaScript库,用于创建交互式数据可视化,它支持各种图表类型,包括散点图、折线图和直方图,在Vue.js应用程序中,Plotly.js可用于创建动态且引人入胜的数据可视化,本文介绍了使用Plotly.js在Vue中创建交互式散点图,需要的朋友可以参考下
    2024-07-07
  • Vue3.0之引入Element-plus ui样式的两种方法

    Vue3.0之引入Element-plus ui样式的两种方法

    本文主要介绍了Vue3.0之引入Element-plus ui样式的两种方法,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02
  • vue.js项目使用原生js实现移动端的轮播图

    vue.js项目使用原生js实现移动端的轮播图

    这篇文章主要为大家介绍了vue.js项目中使用原生js实现移动端的轮播图,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-04-04
  • Vue使用自定义指令实现拖拽行为实例分析

    Vue使用自定义指令实现拖拽行为实例分析

    这篇文章主要介绍了Vue使用自定义指令实现拖拽行为,结合实例形式分析了Vue使用自定义指令实现拖拽行为具体步骤、原理与操作注意事项,需要的朋友可以参考下
    2020-06-06
  • 实时通信Socket io的使用示例详解

    实时通信Socket io的使用示例详解

    这篇文章主要为大家介绍了实时通信Socket io的使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-11-11
  • vue2.0中vue-cli实现全选、单选计算总价格的实例代码

    vue2.0中vue-cli实现全选、单选计算总价格的实例代码

    本篇文章主要介绍了vue2.0中vue-cli实现全选、单选计算总价格的实例代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-07-07
  • vue 项目中实现按钮防抖方法

    vue 项目中实现按钮防抖方法

    这篇文章主要介绍了vue 项目中实现按钮防抖方法,首先需要新建 .js文件存放防抖方法,引入防抖文件,methods中添加方法,本文结合示例代码给大家介绍的非常详细,需要的朋友可以参考下
    2022-12-12
  • 详解Vue-cli代理解决跨域问题

    详解Vue-cli代理解决跨域问题

    本篇文章主要介绍了Vue-cli代理解决跨域问题,详细的介绍了Vue如何设置代理,具有一定参考价值,有兴趣的可以了解一下
    2017-09-09
  • 如何利用vue.js实现拖放功能

    如何利用vue.js实现拖放功能

    这篇文章主要给大家介绍了如何利用vue.js实现拖放功能的相关资料,本文并未使用现有的库,而是使用内置的HTML拖放API来实现简单的拖放系统,需要的朋友可以参考下
    2021-06-06
  • Vue 2源码阅读 Provide Inject 依赖注入详解

    Vue 2源码阅读 Provide Inject 依赖注入详解

    这篇文章主要为大家介绍了Vue 2源码阅读 Provide Inject 依赖注入详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08

最新评论