# 虚拟列表

组件代码:

<template>
  <div
    :class="{ 'v-list': true, 'scroolbar-hide': scroolbarHide }"
    ref="vListRef"
    @scroll.passive="getScroll($event)"
    @mouseenter="scroolbarHide = false"
    @mouseleave="scroolbarHide = true"
  >
    <ul class="ul-style" :style="{ height: allHeight }">
      <li
        class="li-style"
        v-for="item in showData"
        @click="onItem(item)"
        :style="{ top: itemTop(item) }"
      >
        <span>{{ item.label }}</span>
        <span>{{ item.value }}</span>
      </li>
    </ul>
  </div>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted } from 'vue'
type Item = {
  label: string
  value: string
  index: number
}
const props = defineProps({
  data: {
    type: Array<Item>,
    default: () => [],
  },
})
const scroolbarHide = ref(true)
const emits = defineEmits(['on-item'])
const onItem = (item: Item) => emits('on-item', item)
const vListRef = ref(null)
const cacheNum = 2 // 上下多出2个列表项用于加载缓存
const start = ref(cacheNum) // 起始数据
const showCount = ref(10) // 一次5条渲染数据(可根据容器高度动态变化)
const itemHeight = 40 // 每一项的高度
const allHeight = computed(() => itemHeight * props.data.length + 'px')
const showData = computed(() =>
  props.data.slice(start.value - cacheNum, start.value + showCount.value)
)
const itemTop = computed(() => {
  return function (item: Item) {
    return itemHeight * item.index + 'px'
  }
})
const baseHeight = cacheNum * itemHeight
const getScroll = (event: any) => {
  const scrollTop = event.target.scrollTop
  start.value =
    scrollTop > baseHeight ? Math.ceil(scrollTop / itemHeight) : cacheNum
}
onMounted(() => {
  let h = 0
  const div: any = vListRef.value
  if (div) {
    h = div.clientHeight
  }
  showCount.value = Math.floor(h / itemHeight)
})
</script>
<style lang="less" scoped>
.v-list {
  width: 400px;
  height: 100%;
  position: relative;
  overflow: auto;
  .ul-style {
    width: 100%;
    .li-style {
      height: 40px;
      line-height: 40px;
      position: absolute;
      width: 100%;
    }
  }
}
.scroolbar-hide {
  scrollbar-width: none;
  -ms-overflow-style: none;
  &::-webkit-scrollbar {
    display: none;
    width: 0;
    height: 0;
    color: transparent;
  }
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

使用:

<template>
  <div class="home-page">
    <VList :data="listData"></VList>
  </div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import VList from '@/components/v-list/index.vue'
const listData = ref(
  Array.from({ length: 100000 }).map((_, idx) => ({
    label: '标题' + (idx + 1),
    value: '这是虚拟列表' + (idx + 1),
    index: idx,
  }))
)
</script>
<style lang="less" scoped>
.home-page {
  position: relative;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

实现原理:

  • 首先定义每一行的高度,再根据总条数计算出总高度,并设置 ul 的高度为总高度
  • 监听 scroll 滚动条事件,根据滚动距离计算出 start 起始位置
  • 实时计算列表数据,根据 start 和 end
上次更新: 11/28/2023, 7:06:34 PM