-
简介
- 虚拟列表又称为长列表,当有些业务场景不适合用分页且数据量大时,就需要用到虚拟列表
- 虚拟列表其实就是按需显示的一种技术,即对
可见区域
进行渲染。对不可见区域
不渲染或者部分渲染
-
列表项固定高度的虚拟列表
-
实例图解
-
假设现在由10000条数据,可展示区域的高度是500px,列表项的高度是50px,则我们最多能看到10条数据项,即我们每次加载也只加载10条数据即可
-
列表滚动时,可通过计算得出当前可视区可以展示的列表项。比如滚动高度scrollTop=150,那么现在列表展示的就是第4项到第13项
-
-
实现
- 获取列表数据 listData
- 获取列表项的高度 itemHeight
- 获取容器的高度 screenHeight
- 获取列表的高度 listHeight = listData.lenght * itemHeight
- 获取可显示的列表项数 visibleCount = Math.ceil(screenHeight / itemHeight)
- 获取索引的开始位置 startIndex = 0 || Math.floor(scrollTop / itemHeight)
- 获取索引的结束位置 endIndex = startIndex + visibleCount
- 获取可显示的列表数据 visibleData = listData.slice(startIndex, Math.min(endIndex, listData.lenght))
- 获取列表偏移量 startOffset = scrollTop - scrollTop % itemHeight
代码实现:virtual-container的高度是由virtual-phantom来撑开,滚动的时候通过改变
startIndex
和startOffset
来调整可显示的列表数据visibleData
和virtual-list的位置调整<div ref="virtualListRef" class="virtual-container" :style="{ height: screenHeight + 'px' }" @scroll="scrollList" > <div class="virtual-phantom" :style="{ height: listHeight + 'px' }"></div> <div class="virtual-list" :style="{ transform: transform }"> <div v-for="(item, index) in visibleData" class="item"> {{ item.name }} </div> </div> </div>
.virtual-container { width: 100%; height: 100%; border: 1px solid #58a; overflow: auto; position: relative; .virtual-phantom { position: absolute; left: 0; top: 0; right: 0; z-index: -1; } .virtual-list { position: absolute; left: 0; top: 0; right: 0; .item { border-bottom: 1px solid #58a; height: 40px; line-height: 40px; } } }
-
-
列表项动态高度的虚拟列表
- 每项高度不确定,以预估高度高度先进行渲染,再获取真实高度缓存。在onUpdated生命周期函数里面实时更新position
- 实现
-
获取列表数据 listData
-
获取列表项的预估高度 estimatedItemSize
-
获取容器的高度 screenHeight
-
记录每项数据的位置 position
// 初始时,以预估高度给position赋值 position.value = props.virtualList.map((item, index: number) => { return { index, height: props.estimatedItemSize, top: index * props.estimatedItemSize, bottom: (index + 1) * props.estimatedItemSize, }; }); // 实时更新位置 const updatePosition = () => { const nodes = itemRef.value!; nodes.forEach((node) => { const rect = node.getBoundingClientRect(); const height = rect.height; const index = Number(node.id); const oldHeight = position.value[index].height; const diff = height - oldHeight; if (diff) { position.value[index].height = height; position.value[index].bottom = position.value[index].bottom + diff; for (let k = index + 1; k < position.value.length; k++) { position.value[k].top = position.value[k - 1].bottom; position.value[k].bottom = position.value[k].bottom + diff; } } }); };
-
获取列表的高度 listHeight = position[position.lenght - 1].bottom
-
获取可显示的列表项数 visibleCount = Math.ceil(screenHeight / estimatedItemSize)
-
获取索引的开始位置 startIndex;
// 普通查找 const getStartIndex = (scrollTop = 0) => { let item = position.value.find((i) => i && i.bottom > scrollTop); return item.index; };
// 二分查找快速 const getStartIndex = (scrollTop: number) => { let start = 0; let end = position.value.length - 1; let tempIndex = -1; while (start <= end) { let midIndex = parseInt(String((start + end) / 2)); let midValue = position.value[midIndex].bottom; if (midValue === scrollTop) { return midIndex + 1; } else if (midValue < scrollTop) { start = midIndex + 1; } else if (midValue > scrollTop) { if (tempIndex === -1 || tempIndex > midIndex) { tempIndex = midIndex; } end = midIndex - 1; } } return tempIndex; };
-
获取索引的结束位置 endIndex = startIndex + visibleCount
-
缓存容量 buffer =1
-
上方缓存数据量 aboveCount = Math.min(startIndex, buffer * visibleCount)
-
下方缓存数据量 belowCount = Math.min(endIndex, buffer * visibleCount)
-
获取可显示的列表数据 visibleData = listData.slice(startIndex - aboveCount endIndex + belowCount)
-
获取列表偏移量 startOffset
// 获取偏移量 const setStartOffset = () => { let startOffset = 0; // 含有上下缓存区 if (startIndex.value < 1) { startOffset = 0; } else { const size = position.value[startIndex.value].top - (position.value[startIndex.value - aboveCount.value] ? position.value[startIndex.value - aboveCount.value].top : 0); startOffset = position.value[startIndex.value].top - size; } listRef.value!.style.transform = `translate3d(0,${startOffset}px,0)`; };
-
虚拟列表实现
于 2023-02-21 16:45:40 首次发布