Vue虚拟列表

最近项目遇到一个问题,如何加载大量的数据,试了一下,加载几千条dom过多会导致页面太卡,看了网上的解决方案,现在解决多数据渲染,相信大家可能会想到分页,触底加载,懒加载等等,但其实虚拟列表也是多数据高性能加载的一个重要解决方案。

我这边理解也不是很深,讲一下大致的实现思路

首先,理解虚拟列表的原理,一次性加载大量数据,会导致页面dom太多,所以我们每次只加载对应的视窗里面的dom,其他地方的数据不加载,但是为了渲染高度,所以用padding代替,用100条数据理解,视窗高度1000px,一条列表100px,则我们就加载10条数据,剩下的90用padding代替,则设置dom padding-top为0,padding-bottom为 (100 - 10)* 100px,滚动之后,在改变对应的值

思路之后就是实现

1.获取可是区域高度
 

// 获取可视区域的高度
      this.boxHeight = Math.max(document.body.scrollHeight,document.documentElement.scrollHeight);

2.计算每页显示行数

    pageNum() {
      return Math.ceil(this.boxHeight / this.itemHeight);
    },

3.滚动之后计算第一条数据的index,ref为外层dom,if条件是为了滚动太快之后,target计算不准确,可以自己试试就知道了

   startScroll() {
      let target = Math.floor(this.$refs.scrollBox.scrollTop / this.itemHeight);
      if(target > this.allHX.length - this.pageNum){
        this.startNum = this.allHX.length - this.pageNum;
        return
      }
      this.startNum = target;
    },

4.视窗渲染为(start,end),所以需获取结束index

  endNum() {
      return this.startNum + this.pageNum;
    },

5.根据上面的数据获取视窗渲染数据,为了不影响原数组,采用slice

    showHX() {
      return this.allHX.slice(this.startNum, this.endNum);
    }

到这差不多就结束了,然后就是渲染了。

虚拟列表原理我的理解是,每次滚动的时候(滚动距离不超过1条数据高度的时候),数据是不会变化的,此时假设展示10条,则显示0-9,然后滚动超过了1条数据,此时start和end改变,会渲染1-10的数据,但是由于滚动,第一条数据在外面去了,然后padding发生变化,在把它挤到视窗开始的位置。顺序不分先后,不清楚~~

写了个demo

gitee地址:vue虚拟列表: vue虚拟列表

完整代码如下

<template>
  <div class="scroll-main" @scroll="fn" ref="scrollBox">
    <div :style="{paddingTop:startNum * itemHeight + 'px',paddingBottom:(allHX.length  - endNum) * itemHeight + 'px'}">
      <div v-for="(item, index) in showHX" :key="item.id" class="item" :style="{height: (itemHeight - 2) + 'px'}">
        <span>{{item.id}}-{{item.msg}}</span>
        <span @click="deleteItem(index)">删除</span>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      allHX: [],
      startNum: 0,
      boxHeight: 0,
      fn: this.debounce(this.startScroll, 100),
      wishList:['洗脚', '按摩', '给零花钱', '做饭', '买车', '买房', '做项目', '带娃', '应付丈母娘']
    };
  },
  props: {
    itemHeight: {
      type: String,
      default: '100'
    }
  },
  created() {
    this.getMock(100000);
  },
  mounted() {
    this.getBoxHeight();
  },
  methods: {
    getMock(count) {
      for (let i = 0; i < count; i++) {
        this.allHX.push({
          id: `HX${i}号`,
          msg: `负责给kfq${this.wishList[i%9]}`,
        });
      }
    },
    getBoxHeight() {
      // 获取可视区域的高度
      this.boxHeight = Math.max(document.body.scrollHeight,document.documentElement.scrollHeight);
    },
    startScroll() {
      let target = Math.floor(this.$refs.scrollBox.scrollTop / this.itemHeight);
      if(target > this.allHX.length - this.pageNum){
        this.startNum = this.allHX.length - this.pageNum;
        return
      }
      this.startNum = target;
    },
    deleteItem(index) {
      this.allHX.splice(this.startNum + index, 1);
    },
    debounce(fun, wait) {
      let timer  = null;
      return function (){
        if(timer !== null) clearTimeout(timer);
        timer  = setTimeout(fun, wait);
      }
    }
  },
  computed: {
    pageNum() {
      return Math.ceil(this.boxHeight / this.itemHeight);
    },
    endNum() {
      return this.startNum + this.pageNum;
    },
    showHX() {
      return this.allHX.slice(this.startNum, this.endNum);
    }
  }
};
</script>

<style scoped>
.scroll-main{
  height: 100%;
  overflow: scroll;
  overflow-x: hidden;
}
.item{
  display: flex;
  justify-content: space-between;
  align-items: center;
  border: 1px solid red;
  padding: 0 20px;
}
</style>

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值