vue前端虚拟列表

408 阅读2分钟

对于有成千上万条数据列表的渲染,前端页面的初始化加载和滚动操作都会带来一定程度上的卡顿。虚拟列表的原理是只渲染可视区域内的列表元素,在可视区域外的dom元素都不会被渲染。当然这个渲染范围,通过调整也可以大于可视区域。

实现原理

  1. 根据滚动区域的高度和列表项的高度,计算出每次实际需要渲染的列表项的个数。
  2. 设置数组的截取开始点截取结束点,在包含所有数据的数组中截取出实际渲染的数组。
  3. 监听滚动事件,获取scrollTop值,并根据该值重新计算截取开始点截取结束点
  4. 设置虚拟滚动条。在渲染的滚动列表下方,放入一个空div。只需动态设置其高度,使其高度加上已渲染的所有列表项的高度,大于等于所有列表项的高度之和。目的是撑起滚动内容,并获得较为真实的滚动条。实际的计算中会出现误差,可按实际情况进行调整。
  5. 滚动时,已渲染的列表部分会被滚上去,此时,我们借用transform来对其进行向下平移。向下平移的距离为scrollTop值。

实现代码

本文是在vue中实现了虚拟滚动。下面是html部分:

<div id="virtual-container" @scroll="scrollService.onScroll">
    <div
      :style="{ transform: `translateY(${scrollService.tranlateY.value}px)` }"
    >
      <div
        class="virtual-item"
        v-for="item in scrollService.visibleList.value"
        :key="item.id"
      >
        {{ item.name }}
      </div>
    </div>
    <div
      class="virtual-height"
      :style="{ height: scrollService.virtualHeight.value + 'px' }"
    ></div>
  </div>

对应的js部分:

import { ref, computed } from "vue";

export class VirtualScroll {
  // 列表的所有数据
  private scrollList = ref<any[]>([]);

  // 滚动区域高度和列表项高度
  private containerHeight = 555;
  private listItemHeight = 23;

  // 整个列表向上平移的高度
  tranlateY = ref(0);

  // 截取数据的起点和终点
  startIndex = ref(0);
  endIndex = ref(Math.ceil(this.containerHeight / this.listItemHeight));

  // 虚拟高度,用来撑起内容,获取滚动条
  virtualHeight = computed(() => {
    return (
      (this.scrollList.value.length - this.visibleList.value.length) *
        this.listItemHeight +
      this.listItemHeight
    );
  });

  // 实际展示的列表数据
  visibleList = computed(() => {
    return this.scrollList.value.slice(
      this.startIndex.value,
      this.endIndex.value
    );
  });

  constructor(
    list: any[],
    containerHeight: number = 555,
    listItemHeight: number = 23
  ) {
    this.scrollList.value = list;
    this.containerHeight = containerHeight;
    this.listItemHeight = listItemHeight;
  }

  // 滚动函数
  onScroll(e: any) {
    const scrollTop = e.target?.scrollTop;
    this.tranlateY.value = scrollTop;
    this.startIndex.value = Math.floor(scrollTop / this.listItemHeight);
    this.endIndex.value =
      this.startIndex.value +
      Math.ceil(this.containerHeight / this.listItemHeight);
  }
}

vue组件中引入虚拟滚动服务:

import { VirtualScroll } from "./virtualScroll";

const scrollService = new VirtualScroll(scrollList.value);