在开发中经常遇到大量的渲染列表数据问题,往往我们就只是简单地遍历渲染,没有过多地去关注是否会存在性能问题,这导致如果数据量较大的时候,比如上万条数据,将会在dom中渲染上万个节点,这将加大浏览器的开销,可能会导致页面卡顿,加载慢等性能问题。因此,在渲染大量数据时,可以选择使用虚拟列表,只渲染用户可视区域内的dom节点。该组件已开源上传npm,可以直接安装使用,Git地址在文尾。
1、通过传入组件的每条数据的高度,计算整个列表的高度,从而得到滚动列表的总高,并将总高赋值给列表。
2、监听滚动事件,监听外层容器的滚动事件,并确定可视区域内起止数据在总数据的索引值,这可以通过scrollTop来实现。
3、设置数据对应的元素,为每条数据设置一个绝对定位,其中top等于索引值乘以每条数据的高度。
4、考虑缓冲条数,为了避免滑动过快产生空白,可以设置缓冲条数。具体来说,如果滚动到底部,可以只显示最后N条数据,如果滚动到上部,可以只显示前N条数据。
这样,就可以实现一个固定高度的虚拟列表。
原理和固定高度基本一致,差别在于,用户可以预先定义每条数据的高度,在渲染时再动态获取每一条数据的实际高度,从而重新计算滚动列表的总体高度。
showItemList
循环可视区域内的数据+缓存区的数据
<template>
<div class="virtual-wrap" ref="virtualWrap" :style="{
width: width + 'px',
height: height + 'px',
}" @scroll="scrollHandle">
<div class="virtual-content" :style="{height: totalEstimatedHeight +'px'}">
<list-item v-for="(item,index) in showItemList" :key="item.dataIndex+index" :index="item.dataIndex" :data="item.data" :style="item.style"
@onSizeChange="sizeChangeHandle">
<template #slot-scope="slotProps">
<slot name="slot-scope" :slotProps="slotProps">slot>
template>
list-item>
div>
div>
template>
通过可视区域内的开始和结束索引,获取需要渲染的列表数据。
const getCurrentChildren = () => {
//重新计算高度
estimatedHeight(props.itemEstimatedSize,props.itemCount)
const [startIndex, endIndex] = getRangeToRender(props, scrollOffset.value)
const items = [];
for (let i = startIndex; i <= endIndex; i++) {
const item = getItemMetaData(i);
const itemStyle = {
position: 'absolute',
height: item.size + 'px',
width: '100%',
top: item.offset + 'px',
};
items.push({
style: itemStyle,
data: props.data[i],
dataIndex:i
});
}
showItemList.value = items;
}
const getRangeToRender = (props: any, scrollOffset: any) => {
const { itemCount } = props;
const startIndex = getStartIndex(props, scrollOffset);
const endIndex = getEndIndex(props, startIndex + props.buffCount);
return [
Math.max(0, startIndex -1),
Math.min(itemCount - 1, endIndex ),
];
};
const getStartIndex = (props: any, scrollOffset: number) => {
const { itemCount } = props;
let index = 0;
while (true) {
const currentOffset = getItemMetaData(index).offset;
if (currentOffset >= scrollOffset) return index;
if (index >= itemCount) return itemCount;
index++
}
}
const getEndIndex = (props: any, startIndex: number) => {
const { height, itemCount } = props;
// 获取可视区内开始的项
const startItem = getItemMetaData(startIndex);
// 可视区内最大的offset值
const maxOffset = Number(startItem.offset) + Number(height);
// 开始项的下一项的offset,之后不断累加此offset,知道等于或超过最大offset,就是找到结束索引了
let offset = Number(startItem.offset) + startItem.size;
// 结束索引
let endIndex = startIndex;
// 累加offset
while (offset <= maxOffset && endIndex < (itemCount - 1)) {
endIndex++;
const currentItem = getItemMetaData(endIndex);
offset += currentItem.size;
}
// 更新已计算的项的索引值
measuredData.lastMeasuredItemIndex = endIndex;
return endIndex;
};
const estimatedHeight = (defaultEstimatedItemSize = 50, itemCount: number) => {
let measuredHeight = 0;
const { measuredDataMap, lastMeasuredItemIndex } = measuredData;
// 计算已经获取过真实高度的项的高度之和
if (lastMeasuredItemIndex >= 0) {
const lastMeasuredItem = measuredDataMap[lastMeasuredItemIndex];
measuredHeight = lastMeasuredItem.offset + lastMeasuredItem.size;
}
// 未计算过真实高度的项数
const unMeasuredItemsCount = itemCount - measuredData.lastMeasuredItemIndex - 1;
// 预测总高度
totalEstimatedHeight.value = measuredHeight + unMeasuredItemsCount * defaultEstimatedItemSize;
}
1、通过ResizeObserver
在子节点高度变化时触发父组件的方法,重新计算整体高度。
2、通过插槽将每条数据动态插入到列表中。
npm install @fcli/vue-virtually-list --save-dev 来安装
在项目中使用
import VueVirtuallyList from '@fcli/vue-virtually-list';
const app=createApp(App)
app.use(VueVirtuallyList);
示例:
<div class="content">
<vue-virtually-list :data="list" :height="400" :width="600" :itemCount="1000" :itemEstimatedSize="20" :buffCount="50">
<template #slot-scope="{slotProps}">
<div class="li">{{ slotProps.data.text }}div>
template>
vue-virtually-list>
div>
属性 | 属性名称 | 类型 | 可选值 |
---|---|---|---|
data | 列表数据 | Array | [] |
height | 虚拟容器的高度 | number | 0 |
width | 虚拟容器的宽度 | number | 0 |
itemCount | 滚动列表的条数 | number | 0 |
itemEstimatedSize | 预设每行数据的高度 | number | 可不填,组件会动态计算 |
buffCount | 上下缓冲区的条数 | number | 增加快速滚动时的流畅性 |
#slot-scope | 插槽 | object | slotProps.data| |
例:
{{ slotProps.data.text }}
Git地址:https://gitee.com/fcli/vue-virtually-list.git