简单的长列表优化

109 阅读1分钟
问题

一次性渲染过多的数据,会导致页面卡顿。

可以根据以下几个参数,截取显示的数据。

原理

当前滚动窗口高度(viewportHeight),滚动出的距离(scrollTop),每个item的高度(itemHeight),显示窗口之外缓存item个数(buffer),总数据长度(totalLen)

计算截取数据的开始start

Math.max(
Math.floor(scrollTop/itemHeight)-buffer,
0
)

计算结束位置 end

Math.min(
 Math.ceil((scrollTop + viewportHeight) / itemHeight + buffer),
 totalLen
)

为了使滚动条显示正常,需要模拟高度,给滚动区域加padding进行模拟,计算方式如下

`${end * itemHeight}px 0 ${Math.max(
   (totalLen - end) * itemHeight,
   0
  )}px 0`
具体实现

虚拟列表组件(UInfiniteList)

<template>
  <div @scroll="onScroll" style="height: 300px;overflow-y: auto;">
    <div class="x-infinite" ref="container" :style="{padding: padding}">
      <slot :sliceItems="sliceItems"></slot>
    </div>
  </div>
</template>

<script>
import { throttle } from "../util/throttle";
export default {
  props: {
    items: {
      required: true
    },
    itemHeight: {
      required: true,
      type: Number
    }
  },
  data() {
    return {
      buffer: 5,
      scrollTop: 0,
      viewportHeight: 300
    };
  },
  computed: {
    start() {
      return Math.max(
        Math.floor(this.scrollTop / this.itemHeight) - this.buffer,
        0
      );
    },
    end() {
      return Math.min(
        Math.ceil(
          (this.scrollTop + this.viewportHeight) / this.itemHeight + this.buffer
        ),
        this.items.length
      );
    },
    sliceItems() {
      return this.items.slice(this.start, this.end);
    },
    padding() {
      return `${this.start * this.itemHeight}px 0 ${Math.max(
        (this.items.length - this.end) * this.itemHeight,
        0
      )}px 0`;
    }
  },
  methods: {
    onScroll: throttle(function(e) {
      this.scrollTop = e.target.scrollTop;
    })
  }
};
</script>

使用

<template>
  <div>
    <u-infinite-list :items="items" :item-height="80" #default="{ sliceItems }">
      <div v-for="item in sliceItems" :key="item.id" style="height: 60px; border-bottom: 1px solid #ccc">
        {{ item.title }}
      </div>
    </u-infinite-list>
  </div>
</template>

<script>
import UInfiniteList from "../../../components/UInfiniteList.vue";

export default {
  name: "",
  components: {
    UInfiniteList
  },
  data() {
    return {
      items: []
    }
  },
  created() {
    this.items = Array(100).fill(1).map((item, i) => {
      return {
        title: 'key'+i,
        id: i
      }
    })
  },
};
</script>

欢迎批评指正~