Skip to content

长列表优化(Vue) #31

Description

@cookiepool

使用第三方插件

  • vue-virtual-scroller
  • vue-virtual-scroll-list

自己实现

解决的问题

  • 限制列表对用户可见的元素数量。我们把可见区域称为 ViewPort
  • 当列表滚动时,列表中的其他元素怎么由不可见变为可见?
  • 监听列表容器元素的滚动事件,当列表里的元素进入可视区域,则添加到DOM中
  • 问题是如果一直这么滚下去,列表会越来越大。所以需要在列表元素离开 ViewPort 的时候从DOM中移除
  • 问题又来了,由于 ViewPort 刚好是一屏的大小,滚动的时候元素还没来得及渲染,会出现一段时间的空白。解决办法就是上下增加一部分数据渲染。

思路

  • 首先容器的高度、列表内容的高度一定。
  • 找到你要显示的数据的开始索引和结束索引,从总的数据列表里面截取。
  • 计算偏移量,使用transform属性进行移动,否则看不到数据。
  • 列表和占位使用绝对定位,脱离文档流,减少重绘和重排。

先贴一个简单的入门代码:

<template>
  <div class="all-wraper">

    <!-- 容器,这儿我以整个视窗的大小为容器的大小 -->
    <!-- 并且列表里面子项、容器这些的高度一定 -->
    <div ref="list" class="virtual-list-container" 
      @scroll="handleScroll"
      :style="{height: screenHeight + 'px'}"
    >
      <!-- 这个用来占位,让页面形成滚动条,这个占位的高度为所有内容渲染完时容器本应有的高度 -->
      <div :style="{'height': listHeight + 'px'}" class="virtual-list-placehold"></div>

      <!-- 内容的渲染区域 -->
      <div :style="{'transform': getTranslate3d}" class="virtual-list-list">
        <p class="virtual-list-item"
          v-for="item in visibleData"
          :key="item._id"
          :style="{'height': itemHeight + 'px', 'line-height': itemHeight + 'px'}"
        >
        {{ item.name }}
        </p>
      </div>
    </div>
  </div>
</template>

<script>
// 导入数据5000条
import data from '../assets/data.json';

export default {
  data() {
    return {
      itemData: data, // 要渲染的数据
      itemHeight: 40, // 渲染的列表里面的单个项目的高度

      screenHeight: 0, // 可视区域的高度
      startIndex: 0, // 起始索引
      endIndex: 0, // 结束索引
      offSetValue: 0, // 偏移量,如果不偏移列表,你会发现你往下面滚动的时候看不到内容了
    }
  },
  computed: {
    // 列表的总高度
    listHeight() {
      return this.itemData.length * this.itemHeight;
    },
    // 可视区能显示的列表项目的数量
    visibleCount() {
      return Math.ceil(this.screenHeight / this.itemHeight);
    },
    // 获取可视区域要显示的数据
    visibleData() {
      // Math.min那个地方这么写是判断滚动到底部时候
      return this.itemData.slice(this.startIndex, Math.min(this.endIndex, this.itemData.length));
    },
    // 计算偏移量的样式
    getTranslate3d() {
      return `translate3d(0, ${this.offSetValue}px, 0)`;
    }
  },
  mounted() {
    this.screenHeight = window.innerHeight;
    this.startIndex = 0;
    this.endIndex = this.startIndex + this.visibleCount;
  },
  methods: {
    handleScroll() {
      // 当前列表滚动的位置
      let scrollTop = this.$refs.list.scrollTop;
      // 这时起始的索引
      this.startIndex = Math.floor(scrollTop / this.itemHeight);
      // 这时结束的索引
      this.endIndex = this.startIndex + this.visibleCount;
      // 偏移量
      this.offSetValue = scrollTop - (scrollTop % this.itemHeight);
    }
  }
}
</script>

<style scoped>
  .virtual-list-container {
    overflow: auto;
    position: relative;
    -webkit-overflow-scrolling: touch;
  }
  .virtual-list-placehold {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    z-index: -1;
  }
  .virtual-list-list {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    text-align: center;
  }
</style>

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions