一、应用场景
当接口返回数据太多时,前端可使用虚拟列表,实现长列表。
二、原理
只有在屏幕部分元素被显示出来,并且被更新,始终只有固定数量的节点,不会卡顿。
三、效果图
四、思路步骤
- 使用 Object.freeze 冻结对象,极大优化性能
- 生成多个元素的options, 或者动态获取
- 根据onPageScroll生命周期监听页面滚动,得到scrollTop, 就是滚动条滑块的位置距离顶部的高度
- startIndex为显示数组索引最小值, startIndex与 scrollTop 对应关系:
this.startIndex = Math.floor(scrollTop / this.ceilHeight);
- 滚动的时候修改显示的列表,屏幕显示列表为
this.listData.slice(this.startIndex, Math.min(this.endIndex, this.listData.length))
五、代码
点击查看代码
<template>
<!-- 长列表 -->
<view class="list_box" :style="{height:`${listData.length * ceilHeight}px`}">
<!--可视区列表 -->
<!-- transform: `translateY(${offsetDistance}px)` -->
<ul class="list1" ref="list_box" :style="{height: `${renderHeight}px`, top:`${offsetDistance}px` }">
<li v-for="(item, index) in visibleData" :key="index" class="item">
{{ item }}
</li>
</ul>
</view>
</template>
<script>
// 冻结对象 Object.freeze 因为vue的Object.defineProtory 数据劫持 导致数据是双向绑定的 所以需要使用冻结对象, 劫持侦听
export default {
data() {
return {
renderHeight: 0, //用户的视口高度
ceilHeight: 50, //单行高度 rpx
listData: [], //最全数据的列表
// visibleData: [], //可视区显示的数据
startIndex: 0, //起始位置
endIndex: 0, //结束位置
top: 0,
// visibleCount: 0, //
offsetDistance: 0, //偏移量
}
},
onLoad() {
let sysInfo = uni.getSystemInfoSync()
this.renderHeight = sysInfo.windowHeight; //获取设备屏幕的高度
this.listData = Array.from({
length: 200000
}).map((item, index) => this.getMockData(index))
},
mounted() {
// 结束位置:起始位置+可视区显示的item 数量
this.endIndex = this.startIndex + this.visibleCount
},
computed: {
// 可视区显示的item 数量:设备屏幕高度/ 每个item的高度
visibleCount() {
// 向上取整
let count = Math.ceil(this.renderHeight / this.ceilHeight)
return count
},
visibleData() {
// 返回最小的哪个数字 Math.min
let list = this.listData.slice(this.startIndex, Math.min(this.endIndex, this.listData.length))
return Object.freeze(list)
},
},
// 监听页面滚动
onPageScroll({
scrollTop
}) {
// 计算起始位置:距离顶部高度/ 每个item的高度
this.startIndex = Math.floor(scrollTop / this.ceilHeight);
// 计算结束位置
this.endIndex = this.startIndex + this.visibleCount
//计算偏移量(防止闪烁跳跃) ↓
this.offsetDistance = scrollTop - (scrollTop % this.ceilHeight);
},
methods: {
getMockData(index) {
return `index:${index} ${' **** '.repeat(2)}`
}
}
}
</script>
<style>
.list_box {
position: relative;
background-color: #ededed;
}
.list1 {
position: absolute;
left: 0;
right: 0;
}
.item {
height: 50px;
line-height: 50px;
border-bottom: 1px solid #ccc;
text-align: left;
list-style: none;
}
</style>
六、补充
Object.freeze()冻结对象
- 一个大的数据对象里,在你确信它不需要改变的时候,你可以给他freeze(),可以大大的增加性能。
- 也可用作冻结线上的配置文件中的对象,以防有人误改。