vue-virtualList

414 阅读2分钟

看了珠峰老师的课,实现了一下简单的 vue-virtualList

介绍

什么是 vue-virtualList

是在运行在 vue 项目中,为了解决 数据量 过大,导致渲染 DOM 数量过多,影响浏览器性能,为了解决这个问题,出现的一个组件

使用

    <VirtualList :items="items" :remain="remain" :size="size" :variable="true">
        <myItem slot-scope="{item}" :item="item" />
    </VirtualList>

参数含义

itemsremainsizevariable
数据来源可见的数量每个item 的高度是否高度不确定

原理分析

基本原理 主要是利用了 对数据的不断截取,只渲染截取的数据

graph TD
Start --> 传递数据
传递数据-->确定数据截取的起始值
确定数据截取的起始值-->截取数据
发生滚动--> 更改截取数据的起始值
更改截取数据的起始值-->截取数据
截取数据-->渲染页面

html 结构

<div class="viewport" ref="viewport" @scroll="handleScroll">
    <!-- 滚动条 -->
    <div class="scroll-bar" ref="scrollBar"></div>
    <!-- 真实位置 -->
    <div class="scroll-list" :style="{top:this.offset+'px'}"> 
      <div v-for="(item,index) in visiableData" :index="item.id" :vid="item.id" ref="items">
         <slot :item="item"/>
      </div>
    </div>
  </div>

css

.viewport {
  overflow-y: scroll;
  position: relative;
}
.scroll-list {
  position: absolute;
  top: 0;
  width: 100%;
}
  1. 截取数据
        // 起始的 start 为0,end 是 可见的数量
        data(){
            start:0,
            end:this.remain,
        },
        visiableData(){
          return this.items.slice(this.start,this.end)
       },
    
  2. 确定滚动条的高度
 mounted(){
     this.$refs.viewport.style.height =  this.remain * this.size + 'px' 
      // 设置滚动条的高度
     this.$refs.scrollBar.style.height =this.items.length * this.size + 'px'
    
 }

这样的话,简单的原理就实现了,当用户滑动的时候,只需要不断的改变 start 和 end 就可以获取最新的数据

  1. 滚动时候改变 start 和 end 值

获取最外层的滚动高度,高度 / 每一项的高度 就是应该开始的 start 值

    handleScroll(){
        let scrollTop = this.$refs.viewport.scrollTop;
        this.start = Math.floor( scrollTop / this.size )
        this.end = this.start + this.remain;
    }

到此为止,一个简单的虚拟列表滚动就实现了,但是会有一个小问题,当用户下拉较快的时候,会有白屏的问题,因为每次页面上的数据是刚好是用户所能看到的全部,当下拉的时候,需要 js 宏任务执行,才会执行GUI 渲染,这个需要时间,所以我们要渲染的结果要稍微大于用户看到的结果

优化

在原先的 slice 上添加 prevCount 和 nextCount,扩大渲染数量

添加 prevCount 和 nextCount

computed:{
    prevCount(){
      return Math.min( this.start,this.remain )
   },
    nextCount(){
     return Math.min( this.end, this.items.length - this.end )
  },
  visiableData(){
  // start 减小, end 值加大
      let start = this.start - this.prevCount
      let end = this.end + this.nextCount
      return this.items.slice(start,end)
  }
},

滚动的时候需要减去重复的数据,因为滚动的时候提前了,会有一段时间重复数据

    handleScroll(){
        this.start = Math.floor( scrollTop / this.size )
        this.end = this.start + this.remain;
        // 需要把预留出来的偏移量 减去
        // 因为滚动的时候 start 提前了,会有一段时间重复数据
      this.offset = this.start * this.size - this.prevCount * this.size;
    }

**到此为止,基本的结构已经结束了,如果需要使用 variable 不定长的高度的话,比较复杂,以后有时间再说 **