无限长列表渲染

201 阅读1分钟

问题

长列表数据渲染,采用分页加载,功能实现,所带来的的问题是节点在不断的添加,增加浏览器性能压力

我google很久找了一个解决方案,自己动手copy一份vue的demo

<template>
  <div class="main">
    <div class="head"></div>
    <div class="content">
      <div class="infinite-list-wrapper" ref="wrapper" @scroll="scrollHandler">
        <div class="infinite-list-ghost" :style="{ height: contentHeight + 'px' }"></div>
        <div class="infinite-list" :style="{ transform: `translate(0, ${top}px)` }">
          <div
            class="item"
            v-for="(item, index) in visibleData"
            :key="index"
            :style="{ height: `${item.height}px`, lineHeight: `${item.height}px` }">
            {{ `item-${item.val}` }}
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
const randomBoolean = () => Math.random() - 0.5 > 0
const list = []

for (let val = 0; val < 777; val++) {
  // randam height
  const height = randomBoolean() ? 60 : 30

  const obj = { val, height: 60 }

  if (!val) {
    obj.offsetTop = height
  }

  list.push(obj)
}

const contentHeight = list.reduce((p, c) => p + c.height, 0)
export default {
  data () {
    return {
      // 可视区域top
      top: 0,
      // 可见高度
      visibleHeight: 0,
      // 可见列表
      visibleData: [],
      // 上下预加载个数
      offset: 10,
      // 间隔
      interval: 2,
      // 总列表,及offsetTop
      list,
      // 数据总高度
      contentHeight
    }
  },
  mounted () {
    /*
      1. 获取当前盒子的可视区高度
      2. 获得初始化数据
    */
    this.$nextTick(() => {
      const wrapper = this.$refs.wrapper
      wrapper.onscroll = this.scrollHandler
    })
    const visibleHeight = this.$refs.wrapper.clientHeight
    this.visibleHeight = visibleHeight
    const { visibleData, top } = this.doCalculate(0)
    this.visibleData = visibleData
    this.top = top
  },
  methods: {
    scrollHandler (e) {
      const startIndex = this.findStartIndex(e.target.scrollTop)
      if (startIndex % this.interval === 0) {
        const { visibleData, top } = this.doCalculate(startIndex)
        this.visibleData = visibleData
        this.top = top
      }
    },
    findStartIndex (top) {
      let index = 0
      while (index < this.list.length) {
        if (!this.list[index].offsetTop) {
          this.calculateOffset(index)
        }
        if (top < list[index].offsetTop) {
          break
        }
        index++
      }
      return index
    },
    calculateOffset (index) {
      if (index === this.list.length) {
        return
      }

      // 取缓存
      if (this.list[index].offsetTop) {
        return this.list[index].offsetTop
      }

      let offsetTop = this.list[index].height
      // console.log('1111----:::::', index - 1)
      offsetTop = offsetTop + this.calculateOffset(index - 1)
      // console.log('offsetTop::::', offsetTop)
      // 添加缓存
      this.list[index] = {
        ...this.list[index],
        offsetTop
      }
      // console.log(this.list)
      return offsetTop
    },
    doCalculate (startIndex) {
      /*
        预加载数 * 2 + 1 + endIndex = 新的endIndex
      */
      const innerOffset = startIndex = startIndex - this.offset
      // console.log('doCalculate::::', innerOffset)
      startIndex = startIndex > 0 ? startIndex : 0
      let endIndex = this.findEndIndex(startIndex) + this.offset * 2 + 1
      endIndex = innerOffset < 0 ? endIndex + innerOffset : endIndex
      endIndex = endIndex > this.list.length ? this.list.length : endIndex
      const visibleData = this.list.slice(startIndex, endIndex)
      // console.log('doCalculate:::::-endIndex', endIndex)
      const top = this.findTopByIndex(startIndex)
      return {
        visibleData,
        top
      }
    },
    findTopByIndex (index) {
      return index ? this.list[index - 1].offsetTop : 0
    },
    findEndIndex (startIndex) {
      if (this.list[startIndex].endIndex) {
        return this.list[startIndex].endIndex
      }
      let visibleHeight = this.visibleHeight || this.$refs.wrapper.clientHeight
      const endIndex = this.calculateEndIndex(visibleHeight, startIndex)
      this.$set(this.list[startIndex], 'endIndex', endIndex)
      return endIndex
    },
    calculateEndIndex (visibleHeight, index = 0) {
      while (visibleHeight > 0) {
        const i = index + 1
        if (i !== this.list.length) {
          visibleHeight = visibleHeight - this.list[++index].height
        } else {
          break
        }
      }
      return index
    }
  }
}
</script>
<style>
* {
  margin:0;
  padding: 0;
}
html,body,#app {
  height: 100%;
}
.main{
  width:100%;
  height: 100%;
  display: flex;
  flex-direction: column;
}
.head{
  width:100%;
  height: 30px;
  background: red;
}
.content{
  flex:1;
  overflow: hidden;
}
.infinite-list-wrapper {
  width: 100%;
  height: 100%;
  overflow-y: auto;
  position: relative;
}
.infinite-list-ghost {
    position: absolute;
    right: 0;
    width: 1px;
    z-index: -1;
}
.infinite-list {
  overflow: auto;
  width: 100%;
}
.infinite-list .item {
    text-align: center;
    line-height: 30px;
}
.infinite-list .item:nth-child(odd) {
      background-color: #eee;
}
</style>