# 虚拟列表
组件代码:
<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
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
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