虚拟列表

88 阅读1分钟

在业务开发中,列表是非常常见的功能。虽然说我们可以通过后台分页的方式去加载数据,但有时也会遇到一次返回几百条不分页的情况,此时我们就可以采用虚拟列表减少渲染的时间,提高用户体验

什么是虚拟列表?

假设后台一次返回1000条列表数据,需要我们在手机上显示。我们就可以通过结合屏幕的高度和用户滚动的距离计算哪些数据可以显示出来,只显示当前可视区域的数据

1732766494129.jpg

当发生滚动时

1732766648296.jpg

实现

<template>
  <div ref="list" class="infinite-list-container" @scroll="scrollEvent">
    // 设置屏幕的总高度,生成滚动条
    <div class="infinite-list-phantom" :style="{ height: listHeight + 'px' }">
      // 当滚动后,由于`渲染区域`相对于`可视区域`已经发生了偏移,此时需要获取一个偏移量`startOffset`,通过样式控制将`渲染区域`偏移至`可视区域`中
      <div class="infinite-list" :style="{ transform: getTransform }">
        <div
          ref="items"
          class="infinite-list-item"
          v-for="item in visibleData"
          :key="item.id"
          :style="{ height: itemSize + 'px', lineHeight: itemSize + 'px' }"
        >
          {{ item.value }}
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
const listData: any = ref([])
const itemSize = ref(100)
const screenHeight = ref(0)
const startOffset = ref(0)
const start = ref(0)
const end: any = ref(null)

const list: any = ref(null)

onMounted(() => {
  let d = []
  for (let i = 0; i < 1000; i++) {
    d.push({ id: i, value: i })
  }
  listData.value = d

  screenHeight.value = window.innerHeight // 获取屏幕高度
  start.value = 0
  end.value = start.value + visibleCount.value
})

const listHeight = computed(() => {
  return listData.value.length * itemSize.value
})

const visibleCount = computed(() => {
  return Math.ceil(screenHeight.value / itemSize.value)
})

const getTransform = computed(() => {
  return `translate3d(0,${startOffset.value}px,0)`
})

const visibleData = computed(() => {
  return listData.value.slice(start.value, Math.min(end.value, listData.value.length))
})

function scrollEvent() {
  let scrollTop = list.value.scrollTop
  start.value = Math.floor(scrollTop / itemSize.value)
  end.value = start.value + visibleCount.value
  startOffset.value = scrollTop - (scrollTop % itemSize.value)
}
</script>

<style scoped lang="scss">
.infinite-list-container {
  height: 100%;
  overflow: auto;
  position: relative;
  -webkit-overflow-scrolling: touch;
}

.infinite-list-phantom {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  z-index: -1;
}

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

.infinite-list-item {
  padding: 10px;
  color: #555;
  box-sizing: border-box;
  border-bottom: 1px solid #999;
}
</style>