Vue2 - vue-virtual-scroller 长列表优化原理

目录

  • 1,效果展示
  • 2,原理
    • 2.1,滚动条的处理
    • 2.2,展示内容处理
  • 3,实现

vue-virtual-scroller

1,效果展示

1w 条数据无压力,看下初始渲染时间 Rendering 对比:

Vue2 - vue-virtual-scroller 长列表优化原理_第1张图片
Vue2 - vue-virtual-scroller 长列表优化原理_第2张图片

2,原理

目标:只加载在可视容器中的列表项。

实现:

2.1,滚动条的处理

  1. 为了保证滚动条正确显示和滑动,需要按照原本的数据量来计算滑动内容区域 wrapper 的真实高度,
  2. wrapper 外层的 container 作为滑动容器,设置 overflow:auto + 定高(比如500px)。

2.2,展示内容处理

最终目标:只展示可视区域内的 item,所以肯定会对源数据进行截取,需要计算出 startIndexendIndex

  1. 为了保证只显示可视区域内的 item,可通过定位将每个 item 的初始位置都固定在第一行(top: 0; left: 0)。
  2. 在对每个 item 设置不同的偏移量 transform: translateY(index * itemHeight); 就可以正常展示了。
  3. 滑动时,需要根据滑动的距离 container.scrollTop 来重新计算 startIndex。再加上 container.clientHeight 可以计算出 endIndex
  4. 则可得出初始偏移量:transform: translateY(startIndex* itemHeight);

3,实现


<template>
  <div>
    <RecycleScroller :items="list" :itemSize="54" v-slot="{ item }" class="scroller">
      <div class="item-box">
        <span>{{ item.id }}span>
        <span>{{ item.name }}span>
      div>
    RecycleScroller>
  div>
template>

<script>
import RecycleScroller from './components/RecycleScroller.vue'

const arr = []
for (let index = 0; index < 10000; index++) {
  arr[index] = {
    id: 'id' + index,
    name: `name` + index
  }
}

export default {
  components: {
    RecycleScroller
  },
  data() {
    return {
      list: arr
    }
  }
}
script>
<style>
.scroller {
  width: 200px;
  height: 500px;
  overflow: auto;
}

.item-box {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 15px;
}
style>

<template>
  <div class="recycle-container" ref="container" @scroll="setPool">
    <div class="recycle-wrapper" :style="{ height: totalSize }">
      <div v-for="poolItem in pool" :key="poolItem.keyField" class="recycle-item" :style="{ transform: `translateY(${poolItem.position}px)` }">
        <slot :item="poolItem.item">slot>
      div>
    div>
  div>
template>

<script>
export default {
  props: {
    items: {
      // 数据列表
      type: Array,
      default: () => []
    },
    itemSize: {
      // 每条数据的高度
      type: Number,
      default: 50
    },
    keyField: {
      // items 中的唯一标识作为 key
      type: String,
      default: 'id'
    }
  },
  data() {
    return {
      pool: [] // 会被渲染的列表内容
    }
  },
  computed: {
    totalSize() {
      return this.items.length * this.itemSize + 'px'
    }
  },
  methods: {
    setPool() {
      const scrollTop = this.$refs.container.scrollTop
      const clientHeight = this.$refs.container.clientHeight
      
      let startIndex = Math.floor(scrollTop / this.itemSize) || 0
      let endIndex = Math.ceil((scrollTop + clientHeight) / this.itemSize)
      const startPosition = startIndex * this.itemSize
      
      // 每次都重新计算 item 对应的位置。
      this.pool = this.items.slice(startIndex, endIndex).map((item, index) => ({
        item,
        position: startPosition + this.itemSize * index
      }))
    }
  },
  mounted() {
    this.setPool()
  }
}
script>

<style scoped>
.recycle-container {
  overflow: auto;
}
.recycle-wrapper {
  position: relative;
}
.recycle-item {
  position: absolute;
  width: 100%;
  top: 0;
  left: 0;
}
style>

如果担心滑动太快导致的白屏问题(没有计算渲染出来),可以在前后各增加10条数据,一般就没有问题了。

以上。

你可能感兴趣的:(vue2,vue.js,前端,javascript,虚拟滚动,长列表优化)