在工作中,有时会遇到需要一些不能使用分页方式来加载列表数据的业务情况,对于此,我们称这种列表叫做长列表。比如,在一些外汇交易系统中,前端会实时的展示用户的持仓情况(收益、亏损、手数等),此时对于用户的持仓列表一般是不能分页的。

那么我们应该怎么操作才能够提高浏览器渲染的性能呢?下面是一种具体的做法,引用的是云中桥的文章高性能渲染十万条数据

  • 什么是虚拟列表

虚拟列表其实是按需显示的一种实现,即只对可见区域进行渲染,对非可见区域中的数据不渲染或部分渲染的技术,从而达到极高的渲染性能。
假设有1万条记录需要同时渲染,我们屏幕的可见区域的高度为500px,而列表项的高度为50px,则此时我们在屏幕中最多只能看到10个列表项,那么在首次渲染的时候,我们只需加载10条即可。

在这里插入图片描述

说完首次加载,再分析一下当滚动发生时,我们可以通过计算当前滚动值得知此时在屏幕可见区域应该显示的列表项。

假设滚动发生,滚动条距顶部的位置为150px,则我们可得知在可见区域内的列表项为第4项至`第13项。

在这里插入图片描述

实现
虚拟列表的实现,实际上就是在首屏加载的时候,只加载可视区域内需要的列表项,当滚动发生时,动态通过计算获得可视区域内的列表项,并将非可视区域内存在的列表项删除。

  • 计算当前可视区域起始数据索引(startIndex)
  • 计算当前可视区域结束数据索引(endIndex)
  • 计算当前可视区域的数据,并渲染到页面中
  • 计算startIndex对应的数据在整个列表中的偏移位置startOffset并设置到列表上

在这里插入图片描述

由于只是对可视区域内的列表项进行渲染,所以为了保持列表容器的高度并可正常的触发滚动,将Html结构设计成如下结构:

<div class="infinite-list-container">
    <div class="infinite-list-phantom"></div>
    <div class="infinite-list">
      <!-- item-1 --> <!-- 你要进行v-for的地方>
      <!-- item-2 -->
      <!-- ...... -->
      <!-- item-n -->
    </div>
</div>

infinite-list-container 为可视区域的容器
infinite-list-phantom 为容器内的占位,高度为总列表高度,用于形成滚动条
infinite-list 为列表项的渲染区域

接着,监听infinite-list-containerscroll事件,获取滚动位置scrollTop

假定可视区域高度固定,称之为screenHeight
假定列表每项高度固定,称之为itemSize
假定列表数据称之为listData
假定当前滚动位置称之为scrollTop

则可推算出:

  • 列表总高度listHeight = listData.length * itemSize
  • 可显示的列表项数visibleCount = Math.ceil(screenHeight / itemSize)
  • 数据的起始索引startIndex = Math.floor(scrollTop / itemSize)
  • 数据的结束索引endIndex = startIndex + visibleCount
  • 列表显示数据为visibleData = listData.slice(startIndex,endIndex)

当滚动后,由于渲染区域相对于可视区域已经发生了偏移,此时我需要获取一个偏移量startOffset,通过样式控制将渲染区域偏移至可视区域中。

  • 偏移量startOffset = scrollTop - (scrollTop % itemSize);

好了准备工作介绍完毕,现在开始封装组件,下面是封装的代码。

<template>
  <div ref="list" class="infinite-list-container" @scroll="scrollEvent($event)">
    <div class="infinite-list-phantom" :style="{ height: listHeight + 'px' }"></div>
    <div class="infinite-list" :style="{ transform: getTransform }">
      <div ref="items"
        class="infinite-list-item" 
        v-for="item in visibleData" 
        :key="item.id"
        :style="{ height: itemSize + 'px',lineHeight: itemSize + 'px' }"
      >{{ item.value }}</div>
    </div>
  </div>
</template>

<script>
export default {
  name:'VirtualList',
  props: {
    //所有列表数据
    listData:{
      type:Array,
      default:()=>[]
    },
    //每项高度
    itemSize: {
      type: Number,
      default:200
    }
  },
  computed:{
    //列表总高度
    listHeight(){
      return this.listData.length * this.itemSize;
    },
    //可显示的列表项数
    visibleCount(){
      return Math.ceil(this.screenHeight / this.itemSize)
    },
    //偏移量对应的style
    getTransform(){
      return `translate3d(0,${this.startOffset}px,0)`;
    },
    //获取真实显示列表数据
    visibleData(){
      return this.listData.slice(this.start, Math.min(this.end,this.listData.length));
    }
  },
  mounted() {
    this.screenHeight = //this.$el.clientHeight;  这里修改为你那个列表可视的高度
    this.start = 0;
    this.end = this.start + this.visibleCount;
  },
  data() {
    return {
      //可视区域高度
      screenHeight:0,
      //偏移量
      startOffset:0,
      //起始索引
      start:0,
      //结束索引
      end:null,
    };
  },
  methods: {
    scrollEvent() {
      //当前滚动位置
      let scrollTop = this.$refs.list.scrollTop;
      //此时的开始索引
      this.start = Math.floor(scrollTop / this.itemSize);
      //此时的结束索引
      this.end = this.start + this.visibleCount;
      //此时的偏移量
      this.startOffset = scrollTop - (scrollTop % this.itemSize);
    }
  }
};
</script>


<style scoped>
.infinite-list-container {
  height: 100%;
  overflow: auto;
  position: relative;
  -webkit-overflow-scrolling: touch;
}

.infinite-list-phantom {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  z-index: -1;
}

.infinite-list {
  left: 0;
  right: 0;
  top: 0;
  position: absolute;
  text-align: center;
}

.infinite-list-item {
  padding: 10px;
  color: #555;
  box-sizing: border-box;
  border-bottom: 1px solid #999;
}
</style>

listDataitemSize分别是你想传入的数据、每一项数据的高度,.infinite-list-container要记得设置高度(不一定是100%,也可以是你想要设置px的高度),.infinite-list-item可以设置为你想要的样式,其他样式要保持一致。

demo在这里https://codesandbox.io/s/virtuallist-1-rp8pi

好了,以上就是固定宽高渲染十万条数据的方法,不固定宽高的可以看看上面那个作者的文章。

--------------------------------------------------------------------下面介绍如下增加一个搜索框搜索这些数据

2020年4月3日新增搜索关键词高亮功能

<input type="text" v-model="search">

data:{
return{
search:''
}
}
//只需要修改一下computed和添加watch
computed:{
replaceArr () {
      const listData = JSON.parse(JSON.stringify(this.listData))
      const search= this.search
      // 匹配关键字正则
      const replaceReg = new RegExp(search, 'g')
      // 高亮替换v-html值
      const replaceString = `<font color='#F14F4A'>${search}</font>`
      for (let i = 0; i < listData.length; i++) {
        // 开始替换
        listData [i]= listData [i].replace(replaceReg, replaceString)
      }
      return listData 
    }
   _listData(){
         return this.replaceArr.filter(data => {
          return Object.keys(data).some(key => {
            return (
              String(data[key])
                .toLowerCase()
                .indexOf(this.search) > -1
            )
          })
        })
    },
    //列表总高度
    listHeight(){
      return this._listData.length * this.itemSize;
    },
    //可显示的列表项数
    visibleCount(){
      return Math.ceil(this.screenHeight / this.itemSize)
    },
    //偏移量对应的style
    getTransform(){
      return `translate3d(0,${this.startOffset}px,0)`;
    },
    //获取真实显示列表数据 + 模糊搜索
    visibleData(){
      return this._listData.slice(this.start, Math.min(this.end,this._listData.length));
    }
  },
  watch: {
    search() {
      this.$refs.list.scrollTop = 0
    }
  },

现在就可以实现搜索这些数据了。

Logo

前往低代码交流专区

更多推荐