前端虚拟滚动列表
在大型的企业级项目中经常要渲染大量的数据,这种长列表是一个很普遍的场景,当列表内容越来越多就会导致页面滑动卡顿、白屏、数据渲染较慢的问题;大数据量列表性能优化,减少真实dom的渲染
看图:绿色是显示区域,绿色和蓝色中间属于预加载:解决滚动闪屏问题;大致了解了流程在往下看;
实现效果:
先说一下你看到这么多真实dom节点是因为做了预加载,减少滚动闪屏现象,这里写了300行,可以根据实际情况进行截取
实现思路:
虚拟列表滚动大致思路:两个div容器
外层:外部容器用来固定列表容器的高度,同时生成滚动条内层:内部容器用来装元素,高度是所有元素高度的和外层容器鼠标滚动事件 dom.scrollTop 获取滚动条的位置根据每行列表的高以及当前滚动条的位置,利用slice() 去截取当前需要显示的内容重点:滚动条的高度是有内层容器的paddingBottom 和 paddingTop 属性顶起来了,确保滚动条位置的准确性这里鼠标上下滚动会出现闪屏问题:解决方案如下:方案一: 预加载:向下预加载:比如div滚动区域显示30行,就预加载 300行( 即这里 slice(startIndex,startIndex + 300) ),向上预加载:在滚动监听事件函数中(computeRow)判断inner的paddingTop和paddingBottom即可当然这里的download-box的padding有30px像素,在加一个div,overflow:hidded就解决了方案二:缩小滚动范围或者节流时间缩短,这里写的500ms
具体代码
<template><div class="enn"><div class="download-box txt" id="scrollable-div" @scroll="handleScroll"><div id="inner"><div v-for="(item, index) in data2" :key="index" class="line-box"><div :class="{ 'text-box': props.collapsed, 'text-box-samll': !props.collapsed }">{{ item }}</div></div></div></div></div></template><script lang="ts" setup>import { onMounted, PropType, ref } from 'vue';import { useText } from './hooks/useText';const props = defineProps({baseData: {type: Object as PropType<{taskId: string;barcodeName: string;}>,default: {},},collapsed: {type: Boolean,default: true,},type: {type: Boolean,default: false,},});const { data } = useText(props.type);// 这里大数据量数组是 data.geneTexts/*** 虚拟列表滚动大致思路:两个div容器** 外层:外部容器用来固定列表容器的高度,同时生成滚动条** 内层:内部容器用来装元素,高度是所有元素高度的和** 外层容器鼠标滚动事件 dom.scrollTop 获取滚动条的位置** 根据每行列表的高以及当前滚动条的位置,利用slice() 去截取当前需要显示的内容** 重点:滚动条的高度是有内层容器的paddingBottom 和 paddingTop 属性顶起来了,确保滚动条位置的准确性** 这里鼠标上下滚动会出现闪屏问题:解决方案如下:** 方案一: 预加载:** 向下预加载:* 比如div滚动区域显示30行,就预加载 300行( 即这里 slice(startIndex,startIndex + 300) ),** 向上预加载:* 在滚动监听事件函数中(computeRow)判断inner的paddingTop和paddingBottom即可** 当然这里的download-box的padding有30px像素,在加一个div,overflow:hidded就解决了** 方案二:缩小滚动范围或者节流时间缩短,这里写的500ms***/let timer_throttle: any;const throttle = (func: Function, wait?: number) => {wait = wait || 500;if (!timer_throttle) {timer_throttle = setTimeout(() => {func.apply(this);timer_throttle = null;}, wait);}};// 鼠标滚动事件const handleScroll = (event: any) => throttle(computeRow, 100);// 计算当前显示tabconst computeRow = () => {// console.log('距离顶部距离', window.scrollY, geneTexts);let scrollableDiv = document.getElementById('scrollable-div');let topPosition = scrollableDiv.scrollTop;let leftPosition = scrollableDiv.scrollLeft;console.log('垂直滚动位置:', topPosition, '水平滚动位置:', leftPosition);const startIndex = Math.max(0, Math.floor(topPosition / 30));const endIndex = startIndex + 300;data2.value = data.geneTexts.slice(startIndex, endIndex);let inner = document.getElementById('inner');if (topPosition < 2700) {// 向上预计加载,这里判断了三个高度,可以多判断几个,增加流畅度inner.style.paddingTop = topPosition + 'px';inner.style.paddingBottom = (data.geneTexts.length + 2) * 30 - topPosition + 'px';} else if (topPosition + data2.value.length * 30 >= data.geneTexts.length * 30) {// 这里 9000 是 内层div的高度 30 * 300 理解div高度是 padding+div内容高度inner.style.paddingTop = topPosition - 900 + 'px'; //900 是div的高度inner.style.paddingBottom = 0 + 'px';} else {inner.style.paddingTop = topPosition - 2700 + 'px';inner.style.paddingBottom = (data.geneTexts.length + 2) * 30 + 2700 - topPosition + 'px';}};const data2 = ref([]);const init = () => {data2.value = data.geneTexts.slice(0, 300);let inner = document.getElementById('inner');inner.style.paddingTop = 0 + 'px';inner.style.paddingBottom = (data.geneTexts.length + 2) * 30 - 900 + 'px';};</script><style lang="less" scoped>.button-box {margin-bottom: 25px;.flex-type(flex-end);:deep(.ant-btn) {margin-left: 10px;}}.enn {background: #282c34;outline: 1px solid red;padding: 30px 20px;height: 960px;}.download-box {width: 100%;// padding: 30px 20px;outline: 1px solid rgb(17, 0, 255);background-color: #fff;overflow: hidden;.line-box {.flex-type(flex-start);height: 30px;}&.txt {background: #282c34;color: #fff;height: 900px;overflow: auto;}}</style>
替代方案
上面是自己写的,github上面还有好多插件可以用,但各有优劣,根据自己需求选择
如:
vue-virtual-scroller
https://github.com/Akryum/vue-virtual-scroller/tree/0f2e36248421ad69f41c9a08b8dcf7839527b8c2