uniapp实现虚拟列表(定高)

一、应用场景

当接口返回数据太多时,前端可使用虚拟列表,实现长列表。

二、原理

只有在屏幕部分元素被显示出来,并且被更新,始终只有固定数量的节点,不会卡顿。

「本质」:就是用有限的 DOM 渲染 “无限” 的数据

三、效果图
虚拟列表效果图

四、思路步骤

  • 若在vue3中,默认数据是深层响应,可用shallowRef()、shallowReactive()设置浅层响应,减少大型数据相应开销
  • 使用 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()冻结对象

  1. 一个大的数据对象里,在你确信它不需要改变的时候,你可以给他freeze(),可以大大的增加性能。
  2. 也可用作冻结线上的配置文件中的对象,以防有人误改。

「参考」手写js虚拟列表

posted @ 2024-04-12 22:14  会吃鱼的猫123  阅读(1298)  评论(0编辑  收藏  举报