vue实现虚拟列表

发布于:2023-01-04 ⋅ 阅读:(484) ⋅ 点赞:(0)

当列表数据过长,会遇到不使用分页方式来加载长列表的需求。如在数据长度大于 1000 条情况,DOM 元素的创建和渲染需要的时间成本很高,完整渲染列表所需要的时间不可接受,同时会存在滚动时卡顿问题。

解决该卡顿问题的重点在于如何降低长列表DOM渲染成本问题。
1、可以通过后端分页,当我们的列表滑到最底下时,再请求另一页的数据
2、虚拟列表,降低长列表DOM渲染成本问题

虚拟列表的实现

在这里插入图片描述

实现虚拟列表就是处理滚动条滚动后的可见区域的变更,其中具体思路如下:

1.计算当前可见区域起始数据的 startIndex
2.计算当前可见区域结束数据的 endIndex
3.计算当前可见区域的数据,并渲染到页面中
4.计算 startIndex 对应的数据在整个列表中的偏移位置 startOffset,并设置到列表上

基础实现

我们首先要考虑的是虚拟列表的 HTML、CSS 如何实现:
1、列表元素(.list-view)使用相对定位
2、使用一个不可见元素(.list-view-phantom)撑起这个列表,让列表的滚动条出现
3、列表的可见元素(.list-view-content)使用绝对定位,left、right、top 设置为 0

html

<template>
  <div
    class="list-view"
    :style="{
      height: `${height}px`,
    }"
    @scroll="handleScroll"
    ref = 'container'
  >
    <div
      class="list-view-phantom"
      :style="{
        height: contentHeight,
      }"
    ></div>
    <ul ref="content" class="list-view-content">
      <li
        class="list-view-item"
        :style="{
          height: itemHeight + 'px',
        }"
        v-for="(item, index) in vData"
        :key="index"
      >
        {{ item.img }}
      </li>
    </ul>
  </div>
</template>

js

<script lang='ts'>
import { Component, Prop, Vue } from 'vue-property-decorator'
@Component({
  name: 'VList'
})
export default class LkModel extends Vue {
  $refs!: {
    container: any,
    content: any
  };

  @Prop({ type: Array, required: true })
  data!: [];

  height = 400
  itemHeight = 30
  vData = []

  get contentHeight () {
    return this.data.length * this.itemHeight + 'px'
  }

  updateVdata (scrollTop:number):void {
    scrollTop = scrollTop || 0
    // 获取可见区域的可见列表数量
    const vCount = Math.ceil(this.$refs.container.clientHeight / this.itemHeight)
    // 获取可见区域的开始数据索引
    const start = Math.floor(scrollTop / this.itemHeight)
    // 获取可见区域的结束数据索引
    const end = start + vCount
    console.error(start, end)
    // 计算出可见区域的数据,让vue更新
    this.vData = this.data.slice(start, end)
    // 刚列表内容显示在可视区域
    this.$refs.content.style.transform = `translate(0, ${start * this.itemHeight}px)`
  }

  handleScroll ():void {
    const scrollTop = this.$refs.container.scrollTop
    this.updateVdata(scrollTop)
  }

  mounted ():void {
    this.updateVdata()
  }
}
</script>

css

<style lang='less' scoped>
.list-view {
  overflow: auto;
  position: relative;
  width: 200px;
  border: 1px solid #aaa;
}
.list-view-phantom {
  position:absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: -1;
}
.list-view-content {
  position:absolute;
  top: 0;
  left: 0;
  right: 0;
}
.list-view-item {
  padding: 5px;
  color:#666;
  line-height: 30px;
  box-sizing: border-box;
}
</style>

原文路径


网站公告

今日签到

点亮在社区的每一天
去签到