虚拟列表的实现(简单易懂)

起因:

app开发过程中遇到需要渲染3000行的列表,页面直接卡顿,所以开始研究起虚拟列表

实现前提条件:

item项等高列表

实现思路:

首先是dom结构:

  1. 定义一个容器(固定高度),监听滚动事件
  2. 容器定义一个内部滚动容器,高度等于列表个数 * 每项高度
  3. 内部滚动容器里面再定义一个容器,具有transform: translateY属性,根据用户滚动动态改变位置,是真正展示的窗口
  4. 再里面就是展示的item项列表

然后是实现逻辑:

  1. 默认展示50条(条数不固定,保证展示能铺满屏幕即可),渲染完成后获取item项高度和最外面容器高度,根据它俩计算出应该展示的item项 Math.ceil(this.containerHeight / this.itemHeight)
  2. 用户滚动时在滚动事件里根据scrollTop设置translateY属性,用scrollTop % this.itemHeight去掉多余高度,保证会在边界上 translateY = scrollTop - (scrollTop % this.itemHeight)
  3. 用户滚动时在滚动事件里根据scrollTop和item项高度判断应该展示的起始项index startIndex = Math.floor(scrollTop / this.itemHeight)
  4. 最后重新渲染列表item项,渲染 startIndex + Math.ceil(this.boxHeight / this.itemHeight)之间的item项

代码展示:

<script lang="ts" setup name="virtual-list-comp">
import { onBeforeMount, ref, computed, nextTick, Ref, onMounted } from 'vue'
import { throttle } from '@/util/util'

const props = defineProps({
  list: {
    type: Array,
    default: () => [],
  },
})

// 容器高度
const containerHeight = ref(0)
// item项高度(默认取第一个)
const itemHeight = ref(0)
const getHeight = computed(() => {
  return itemHeight.value > 0 ? props.list.length * itemHeight.value + 'px' : 'auto'
})

// 真正展示的列表
const showList: Ref<any[]> = ref([])
// 需要渲染的起始项
const startIndex = ref(0)
const itemNum = computed(() => {
  if (containerHeight.value && itemHeight.value) {
    return Math.ceil(containerHeight.value / itemHeight.value)
  }
  return 50
})
const translateY = ref(0)

const initShowList = () => {
  showList.value = props.list.slice(startIndex.value, startIndex.value + itemNum.value)
}
// 获取盒子高度和item项高度
const initHeight = () => {
  containerHeight.value = document.querySelector('.virtual-list')?.clientHeight || 0
  itemHeight.value = document.querySelector('.virtual-list__item')?.clientHeight || 0
}

const handleScroll = (e: any) => {
  const { scrollTop } = e.target

  // 获取 virtual-list__transform 这个展示视口的偏移值
  translateY.value = scrollTop - (scrollTop % itemHeight.value)

  startIndex.value = Math.floor(scrollTop / itemHeight.value)

  initShowList()
}

onBeforeMount(async () => {
  initShowList()
  await nextTick()
  initHeight()
})
onMounted(() => {
  document
    .querySelector('.virtual-list__container')
    ?.addEventListener('scroll', throttle(handleScroll, 10))
})
</script>

<template>
  <div class="virtual-list h-full">
    <div
      class="virtual-list__container overflow-y-scroll"
      :style="{
        height: containerHeight + 'px',
      }"
    >
      <div class="virtual-list__scroll" :style="{ height: getHeight }">
        <div class="virtual-list__transform" :style="{ transform: `translateY(${translateY}px)` }">
          <div v-for="(item, index) of showList" :key="index" class="virtual-list__item">
            <slot name="item" :item="item"></slot>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

你可能感兴趣的:(vue,javascript,前端)