Vue虚拟列表

334 阅读1分钟
// .vue
<div class="home">
    <div ref="container" :style="containerStyle" @scroll="onScroll">
      <div class="wrapper" :style="wrapperStyle">
        <div
          v-for="ele in list"
          :key="ele.index"
          :style="{
            height: '72px',
            border: '1px solid #e8e8e8',
            marginBottom: '8px'
          }"
        >
         Row: {{ ele.data }}
       </div>
     </div>
   </div>
</div>

// /mixin/virtualList.js
export default {
  data() {
    return {
      valueList: [],
      start: 0,
      end: 10,
      itemHeight: 80, // 元素高度
      overscan: 3 // 多渲染数
    };
  },
  computed: {
    list() { // 实际渲染数量
      let valueList = this.valueList;
      let start = this.start;
      let end = this.end;
      return valueList.slice(start, end).map((item, index) => {
        return {
          data: item,
          index: index + start
        };
      });
    },
    totalHeight() { // 元素总高度
      let that = this;
      return that.list.length * that.itemHeight;
    },
    offsetTop() { // 距顶部的距离
      return this.start * this.itemHeight;
    },
    containerStyle() { // 外层样式
      return {
        overflowY: "auto",
        height: "100%"
      };
    },
    wrapperStyle() { // 列表包裹样式
      return {
        width: "100%",
        paddingTop: this.offsetTop + "px"
      };
    }
  },
  methods: {
    getOffset(scrollTop) {
      // 滚动到可见区域外的元素数量
      return Math.floor(scrollTop / this.itemHeight) + 1;
    },
    getViewCapacity(containerHeight) {
      // 可见区域内能显示的数量
      return Math.ceil(containerHeight / this.itemHeight);
    },
    calculateRange() {
      // 计算实际渲染数据
      let that = this;
      const element = that.$refs.container;
      if (element) {
        const offset = that.getOffset(element.scrollTop);
        const viewCapacity = that.getViewCapacity(element.clientHeight);
        const overscan = that.overscan;
        const list = that.valueList;
        // 重新计算开始,滚动到到显示区域外的数量,减去预渲染数量,最小值为0
        const from = offset - overscan;
        const start = from < 0 ? 0 : from;
        // 重新计算结束,最大值为实际数据列表的长度
        const to = offset + viewCapacity + overscan;
        const end = to > list.length ? list.length : to;
        // 滚动到最后了,可以做增加原始数据的操作等
        if (to === overscan + list.length + 1) {
          const scrollBottom = that.scrollBottom;
          scrollBottom && scrollBottom();
        }
        that.start = start
        that.end = end;
      }
    },
    onScroll(e) {
      e.preventDefault();
      this.calculateRange();
    },
    scrollTo(index) {
      const element = this.$refs.container;
      if (element) {
        element.scrollTop = index * this.itemHeight;
        this.calculateRange();
      }
    }
  }
}