blog icon indicating copy to clipboard operation
blog copied to clipboard

Vue虚拟滚动实现原理

Open wuxianqiang opened this issue 4 years ago • 0 comments

已知高度的处理方案

<template>
  <div class="viewport" ref="viewport" @scroll="handleScroll">
    <div class="scroll-bar" ref="scrollBar">
      <!-- 可以滚动的容器 -->
    </div>
    <div class="scroll-list" :style="{transform: `translate3d(0, ${offset}px, 0)`}">
      <!-- 可视区的列表 -->
      <div v-for="item in visibleData" :key="item.id">
        <slot :item="item"></slot>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    size: {
      type: Number,
      default: 0 // 每一项的高度
    },
    remain: {
      type: Number,
      default: 0 // 可视化区域显示多少个
    },
    items: {
      type: Array,
      default: () => [] // 列表
    }
  },
  data() {
    return {
      start: 0,
      offset: 0,
      end: this.remain // 默认显示8个
    };
  },
  computed: {
    prevCount() {
      return Math.min(this.start, this.remain)
    },
    nextCount () {
      // 总个数不足就使用剩下的个数
      return Math.min(this.end, this.items.length - this.end)
    },
    visibleData() {
      let start = this.start - this.prevCount;
      let end = this.end + this.nextCount;
      return this.items.slice(start, end);
    }
  },
  mounted() {
    this.$refs.viewport.style.height = this.size * this.remain + "px";
    this.$refs.scrollBar.style.height = this.items.length * this.size + "px";
  },
  methods: {
    handleScroll() {
      let scrollTop = this.$refs.viewport.scrollTop; // 滚动了多少距离
      this.start = Math.floor(scrollTop / this.size); // 第几个开始,向下取整,因为还有即使还有一半没显示也要保留
      this.end = this.start + this.remain;
      // 去掉滚动完成的那几个
      this.offset = this.start * this.size - this.size * this.prevCount; // 显示三屏,要减去预留渲染
    }
  }
};
</script>

<style>
.viewport {
  overflow: auto;
  position: relative;
}
.scroll-list {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  transform: translate3d(0, 0, 0);
}
</style>

使用

<template>
  <div class="hello">
    <virtual-list  :size="40" :remain="8" :items="items">
      <template slot-scope="{item}">
        <div class="item">
          {{item.value}}
        </div>
      </template>
    </virtual-list>
  </div>
</template>

wuxianqiang avatar Apr 06 '20 03:04 wuxianqiang