虚拟列表的两种实现

134 阅读1分钟

一、使用 transform

<template>
  <div ref="list" class="virtural-list-container" @scroll="scrollEvent">
    <div class="virtural-list-phantom" :style="{ height: listHeight + 'px' }"></div>
    <div class="virtural-list" :style="{ transform: getTransform }">
      <div
        v-for="item in visibleData"
        :key="item.id"
        class="virtural-list-item"
        :style="{ height: itemHeight + 'px', lineHeight: itemHeight + 'px' }"
      >
        <span>{{ item.value }}</span>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'VirturalList1',
  data() {
    return {
      listData: [],
      itemHeight: 50,
      screenHeight: 0,
      curOffset: 0,
      startIndex: 0,
    }
  },
  computed: {
    visibleData() {
      return this.listData.slice(this.startIndex, Math.min(this.endIndex, this.listData.length))
    },

    listHeight() {
      return this.listData.length * this.itemHeight
    },

    visibleCount() {
      return Math.ceil(this.screenHeight / this.itemHeight)
    },

    getTransform() {
      return `translate3d(0, ${this.curOffset}px, 0)`
    },

    endIndex() {
      return this.startIndex + this.visibleCount
    },
  },
  mounted() {
    this.init()
  },
  methods: {
    init() {
      this.getData()
      this.screenHeight = this.$el.clientHeight
      this.startIndex = 0
    },

    async getData() {
      const request = () => {
        return new Promise((resolve) => {
          let data = []
          for (let i = 0; i < 10000; i++) {
            data.push({
              id: i,
              value: `列表内容${i}`,
            })
          }
          setTimeout(() => {
            resolve({
              code: 200,
              data,
              msg: 'Success',
            })
          })
        })
      }

      const res = await request()
      if (res.code === 200) {
        this.listData = res.data
      }
    },

    scrollEvent() {
      const scrollTop = this.$refs.list.scrollTop
      this.startIndex = Math.floor(scrollTop / this.itemHeight)
      this.curOffset = scrollTop - (scrollTop % this.itemHeight)
    },
  },
}
</script>

<style lang="less" scoped>
.virtural-list-container {
  position: relative;
  width: 400px;
  height: 600px;
  overflow: auto;
  border: 1px solid #eee;

  .virtural-list-phantom {
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
  }

  .virtural-list {
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    text-align: center;

    &-item {
      padding: 10px;
    }

    .virtural-list-item + .virtural-list-item {
      border-top: 1px solid #eee;
    }
  }
}
</style>

二、使用paddingTop和paddingBottom

<template>
  <div ref="list" class="virtural-list-container" @scroll="scrollEvent">
    <div class="virtural-list" :style="{ paddingTop: paddingTop + 'px', paddingBottom: paddingBottom + 'px' }">
      <div
        v-for="item in visibleData"
        :key="item.id"
        class="virtural-list-item"
        :style="{ height: itemHeight + 'px', lineHeight: itemHeight + 'px' }"
      >
        <span>{{ item.value }}</span>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'VirturalList2',
  data() {
    return {
      listData: [],
      itemHeight: 50,
      screenHeight: 0,
      startIndex: 0,
    }
  },
  computed: {
    visibleData() {
      return this.listData.slice(this.startIndex, Math.min(this.endIndex, this.listData.length))
    },

    visibleCount() {
      return Math.ceil(this.screenHeight / this.itemHeight)
    },

    paddingTop() {
      return this.startIndex * this.itemHeight
    },

    paddingBottom() {
      const bottom = this.listData.length - this.startIndex - this.visibleCount
      return (bottom > 0 ? bottom : 0) * this.itemHeight
    },

    endIndex() {
      return this.startIndex + this.visibleCount
    },
  },
  mounted() {
    this.init()
  },
  methods: {
    init() {
      this.getData()
      this.screenHeight = this.$el.clientHeight
      this.startIndex = 0
    },

    async getData() {
      const request = () => {
        return new Promise((resolve) => {
          let data = []
          for (let i = 0; i < 10000; i++) {
            data.push({
              id: i,
              value: `列表内容${i}`,
            })
          }
          setTimeout(() => {
            resolve({
              code: 200,
              data,
              msg: 'Success',
            })
          })
        })
      }

      const res = await request()
      if (res.code === 200) {
        this.listData = res.data
      }
    },

    scrollEvent() {
      const scrollTop = this.$refs.list.scrollTop
      this.startIndex = Math.floor(scrollTop / this.itemHeight)
    },
  },
}
</script>

<style lang="less" scoped>
.virtural-list-container {
  position: relative;
  width: 400px;
  height: 600px;
  overflow: auto;
  border: 1px solid #eee;

  .virtural-list {
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    text-align: center;

    &-item {
      padding: 10px;
    }

    .virtural-list-item + .virtural-list-item {
      border-top: 1px solid #eee;
    }
  }
}
</style>