前言
虚拟列表是前端长列表优化的一个解决方案,就是将一个需要渲染所以列表项的长列表,改为只渲染可视区域内的列表项,但滚动效果还是要和渲染所有列表项的长列表一样。
使用场景: 在移动端使用下拉加载数据的时候,随着不断从服务端拉取数据,数据列表内容越来越多,导致创建了很多的节点,这个时候vue的diff就需要对比很多次,造成性能消耗和内存占用。
长列表渲染大量节点导致的问题:
- 占用大量 GPU 资源,导致卡顿
- 节点越多,就越耗费性能
虚拟列表的实现分两种:
下面以列表项高度固定为例子
实现过程
在编码之前需要了解的信息:
- 容器高度(可视区高度):viewport
- 列表项高度:itemSize
- 可视区域展示列表个数: viewCount
- 列表长度:phantomHeight (itemSize * 列表个数)
- 第一个元素顶部的距离:startOffset
- 开始元素的下标: startIndex
- 结尾元素的下标: endIndex
关键点就是确定需要渲染个列表个数,然后根据滚动时动态改变startIndex、endIndex、startOffset值,然后对列表项数据进行过滤切割,获取需要渲染的数据列表。
实现代码如下:
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
| <script setup lang='ts'> import { PropType, ref, computed } from 'vue' interface listItem { index: number, num: number } const props = defineProps({ data: { type: Array as PropType<listItem[]>, default: () => {} } }) const viewport = ref<HTMLElement>() let startIndex = ref<number>(0) let itemSize = 100 let viewCount = 10 let phantomHeight = itemSize * props.data.length
let startOffset = computed(() => { return startIndex.value * itemSize })
let endIndex = computed(() => { return startIndex.value + viewCount }) let filterData = computed(() => { return props.data.slice(startIndex.value, endIndex.value) }) const scrollListBox = () => { startIndex.value = Math.floor((viewport.value?.scrollTop || 0) / itemSize) } </script>
<template> {{startIndex}} {{endIndex}} <div class="viewport" @scroll="scrollListBox" ref="viewport"> <div class="v-list" :style="{ height: phantomHeight + 'px', paddingTop: startOffset + 'px'}"> <div class="v-list-item" v-for="item in filterData" :key="item.num"> {{item.num}} </div> </div> </div> </template>
<style scoped> .viewport { width: 500px; height: 500px; overflow: scroll; } .v-list-item { height: 100px; width: 100%; background: #fff; color: #000; line-height: 100px; border-bottom: 1px solid #ccc; } </style>
|