前端: 如何优化列表大批量的数据渲染
需求点:如何列表数据渲染进行优化?
最近业务上也碰到这个问题点。上网也查了查资料,貌似也经常问,特此写文章记录下来。
关于如何处理以上上面的业务痛点:
就两点:
1 、虚拟列表是最主流的解决方案,不渲染所有的数据,只渲染可视区域中的数据。当用户滑(滚)动时,通过监听 scroll 来判断是上滑还是下拉,从而更新数据。同理 IntersectionObserver 和 getBoundingClientRect 都能实现
2、时间分片主要是分批渲染DOM,使用 requestAnimationFrame 来让动画更加流畅
tips: 第二点就不详细讲述了,文末有链接对应,可自行享用。
话不多说直接上代码:不懂的可下方评论或者私信:
<template>
<div
ref="list"
:style="{height}"
class="infinite-list-container"
@scroll="scroll($event)"
>
<div
ref="phantom"
class="infinite-list-phantom"
/>
<div
ref="content"
class="infinite-list"
>
<div>
<div
v-for="item in visibleData"
ref="items"
:id="item._index"
:key="item._index"
class="infinite-list-item"
>
<!-- <slot ref="slot" :item="item.item"></slot> -->
<p>
<span style="color:red">{{ item.id }}</span>
{{ item.value }}
</p>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
/** 需要渲染的数据 */
listData: {
type: Array,
default: () => []
},
/** 每项高度 */
itemSize: {
type: Number,
default: 200
},
/** 视口高度 */
height: {
type: String,
default: '100%'
}
},
data() {
return {
screenHeight: 0, // 可视区域高度
startOffset: 0, // 偏移量
start: 0, // 起始索引
end: null // 结束索引
};
},
computed: {
/** 列表总高度 */
listHeight() {
return this.listData.length * this.itemSize;
},
/** 可显示的列表项数 */
visibleCount() {
return Math.ceil(this.screenHeight / this.itemSize);
},
/** 偏移量对应的style */
getTransform() {
return `transform3d(0, ${this.startOffset}px, 0)`;
},
/** 可显示列表数据 */
visibleData() {
return this.listData.slice(this.start, Math.min(this.end, this.listData.length));
}
},
mounted() {
this.screenHeight = this.$el.clientHeight;
this.start = 0;
this.end = this.start + this.visibleCount;
},
created() {
this.initPositions();
},
updated() {
this.$nextTick(function() {
if (!this.$refs.items || !this.$refs.items.length) {
return;
}
// 获取真实元素大小,修改对应的尺寸缓存
this.updateItemsSize();
// 更新列表总高度
let height = this.positions[this.positions.length - 1].bottom;
this.$refs.phantom.style.height = height + 'px';
// 更新真实偏移量
this.setStartOffset();
});
},
methods: {
/** 滚动事件 */
scroll(e) {
// 当前滚动位置
let scrollTop = this.$refs.list.scrollTop;
// 此时的开始索引
this.start = this.getStartIndex(scrollTop);
// 此时的结束索引
this.end = this.start + this.visibleCount;
// 此时的偏移量
this.setStartOffset();
},
initPositions() {
this.positions = this.listData.map((d, index) => ({
index,
height: this.estimatedItemSize,
top: index * this.estimatedItemSize,
bottom: (index + 1) * this.estimatedItemSize
}));
},
/** 获取列表起始索引 */
getStartIndex(scrollTop = 0) {
return this.binarySearch(this.positions, scrollTop);
},
/** 二分查找 */
binarySearch(list, value) {
let start = 0;
let end = list.length - 1;
let tempIndex = null;
while (start <= end) {
let midIndex = parseInt((start + end) / 2);
let midValue = list[midIndex].bottom;
if (midValue === value) {
return midIndex + 1;
} else if (midValue < value) {
start = midIndex + 1;
} else if (midValue > value) {
if (tempIndex === null || tempIndex > midIndex) {
tempIndex = midIndex;
}
end = end - 1;
}
}
return tempIndex;
},
/** 获取当前列表项的当前尺寸 */
updateItemsSize() {
let nodes = this.$refs.items;
nodes.forEach(node => {
let rect = node.getBoundingClientRect();
let height = rect.height;
let index = +node.id.slice(1);
let oldHeight = this.positions[index].height;
let dValue = oldHeight - height;
// 存在差值
if (dValue) {
this.positions[index].bottom = this.positions[index].bottom - dValue;
this.positions[index].height = height;
for (let k = index + 1; k < this.positions.length; k++) {
this.positions[k].top = this.positions[k - 1].bottom;
this.positions[k].bottom = this.positions[k].bottom - dValue;
}
}
});
},
/** 获取当前位置的便宜量 */
setStartOffset() {
let startOffset = this.start >= 1 ? this.positions[this.start - 1].bottom : 0;
this.$refs.content.style.transform = `translate3d(0,${startOffset}px,0)`;
}
}
};
</script>
<style lang="less" scoped>
.infinite-list-container {
overflow: auto;
position: relative;
-webkit-overflow-scrolling: touch;
}
.infinite-list-phantom {
position: absolute;
left: 0;
top: 0;
right: 0;
z-index: -1;
}
.infinite-list {
left: 0;
right: 0;
top: 0;
position: absolute;
}
.infinite-list-item {
padding: 5px;
color: #555;
box-sizing: border-box;
border-bottom: 1px solid #999;
}
</style>

浙公网安备 33010602011771号