<template>
  <el-scrollbar ref="scrollbar" class="el-virtual-scroller">
    <!-- 滚动区域 -->
    <div
        class="el-virtual-scroller-content"
        :style="{ height: data.length * ceilSize + 'px' }"
    >
      <div v-for='ceil in list' :key='ceil[nodeKey]' class='el-virtual-scroller-ceil' :style="{transform: `translateY(${ceilSize * ceil._scrollIndex}px)`}">
        <slot v-bind='{row: ceil, $index: ceil._scrollIndex}'></slot>
      </div>
    </div>
</el-scrollbar>
</template>

<script>
import ElScrollbar from '@ued_fpi/element-ui/packages/scrollbar';
export default {
  name: 'ElVirtualScroller',
  props: {
    // 需要渲染的数据
    data: {
      type: Array,
      default: () => []
    },
    // 模式
    mode: {
      type: String,
      default: 'vertical'
    },
    // 单个数据的高度
    ceilSize: {
      type: Number,
      default: 26
    },
    nodeKey: {
      type: String,
      default: 'id'
    },
    // 冗余量
    redundancy: {
      type: Number,
      default: 10
    },
    // 距离边界的标准ceilSize
    boundaryDistance: {
      type: Number,
      default: 5
    }
  },
  data() {
    return {
      list: [], // 实际上被渲染的元素列表
      scrollbarEl: undefined, // 元素
      visibleSize: 0, // 容器大小
      curStartIndex: 0, // 当前显示区域的开始索引(包含了冗余)
      curEndIndex: 0 // 当前显示区域的末尾索引(包含了冗余)
    };
  },
  watch: {
    data() {
      // console.log('watch', new Date().getTime());
      this.renderDom();
    }
  },
  components: {
    ElScrollbar
  },
  mounted() {
    // 根据总数据量和单个节点高度，计算总高度
    // this.totalHeight = this.ceilSize * this.flatData.length
    // 获得当前容器高度
    this.visibleSize = this.$el.offsetHeight;
    this.scrollbarEl = this.$refs.scrollbar.wrap;
    // 添加滚动监听
    this.addListenerScroll();
    this.renderDom();
  },
  beforeDestroy() {
    // 移除事件监听
    this.removeListenerScroll();
  },
  methods: {
    /**
     * 添加滚动条滚动事件监听
     */
    addListenerScroll() {
      this.scrollbarEl.addEventListener('scroll', this.scrollEvent);
    },
    /**
     * 移除滚动条滚动事件监听
     */
    removeListenerScroll() {
      this.scrollbarEl.removeEventListener('scroll', this.scrollEvent);
    },
    /**
     * 滚动事件,
     * 根据当前滚动的距离，计算需要显示的索引
     */
    scrollEvent() {
      // 计算是否靠近边界
      const scrollTop = this.getScrollTop();
      const curIndex = Math.ceil(scrollTop / this.ceilSize);
      // 当前可视区域总数
      const visibleSize = Math.ceil(this.visibleSize / this.ceilSize);

      // 未达到设定的边界值，不用重新渲染

      if (curIndex - this.curStartIndex > this.boundaryDistance && this.curEndIndex - visibleSize - curIndex > this.boundaryDistance) {
        return;
      }

      this.renderDom();
    },
    /**
     * dom渲染方法
    */
    renderDom() {
      const scrollTop = this.getScrollTop();
      // 根据滚动距离，计算顶部节点索引
      // 在计算完底部节点索引后，需要在顶部节点的索引基础上加上冗余量
      const startIndex = Math.ceil(scrollTop / this.ceilSize);
      // 根据容器高度，顶部节点索引和冗余量，获取底部节点索引
      this.curEndIndex =
                Math.ceil(this.visibleSize / this.ceilSize) + startIndex + this.redundancy;
      this.curStartIndex = startIndex - this.redundancy;
      this.list = this.data.filter((item, index) => {
        item._scrollIndex = index;
        // 实际上渲染的节点
        return index > this.curStartIndex && index <= this.curEndIndex;
      });
    },
    /**
     * 获取滚动高度
    */
    getScrollTop() {
      return this.$refs.scrollbar.wrap.scrollTop;
    },
    /**
     * 设置滚动高度
    */
    setScrollTop(distance = 0) {
      this.$refs.scrollbar.wrap.scrollTop = distance;
    }
  }
};
</script>