uniapp长列表虚拟加载实例(前端处理上万条数据加载优化,不卡,纯手敲)
//temple部分
<template>
<!-- 本页面是虚拟加载组件,还有个长列表懒加载组件(sickListPageLoad.vue)。 -->
<view class="sickBody">
<scroll-view scroll-y="true" class="scroll-Y" @scroll="scroll">
<view class="parentDom">
<!-- <view :style="{ height: sickAllList.length * 40 + 'px' }"></view> -->
<view :style="{ height: screenHeight + 'px' }"></view>
<view class="positionRelative" :style="{ transform: getTransform }">
<view v-for="item in visibleData" :key="item.id" class="height40" @click="choseSick(item)">{{
item.name
}}</view>
</view>
</view>
</scroll-view>
</view>
</template>
js部分
let testdata = [];
for (let i = 0; i < 300; i++) {
testdata.push({ id: i, name: '疾病' + i });
}
/**
* 搜索组件
*/
import { throttle } from '@/utils/optimize'; //防抖,自己写一个
export default {
props: {
// 是否展示搜索按钮
listData: {
type: Array,
default: () => []
},
itemSize: {
type: Number,
default: 40
}
},
data() {
return {
searchList: [],
// screenHeight: 1200,
startOffset: 0,
start: 0,
end: 20,
scrollTData: 0,
count: 20,
remain: 8
// testData: []
};
},
computed: {
listHeight() {
return testdata.length * this.itemSize;
},
screenHeight() {
return testdata.length * Number(this.itemSize) + 100;
},
// 前面预留几个
prevCount() {
return Math.min(this.start, this.remain);
},
// 后面预留几个
nextCount() {
return Math.min(this.remain, this.end);
},
getTransform() {
return `translate3d(0,${this.startOffset}px,0)`;
},
visibleData() {
return testdata.slice(this.start, Math.min(this.end, testdata.length));
}
},
mounted() {
this.sickAllList = testdata;
},
methods: {
close() {
this.$emit('popClose');
},
center() {
this.$emit('popCenter');
},
go_search() {
this.$wxPromise.navigateTo({
url: '/pages/searchDrug/index'
});
},
choseSick(item) {
this.$emit('choseSickSearch', item);
},
scroll(e) {
this.scrollTData = e.target.scrollTop;
this.scrollThrottle();
},
/* eslint-disable */
scrollThrottle: throttle(function () {
// uni.showLoading({
// title: '加载中',
// mask: true
// });
let scrollTop = this.scrollTData; // e.target.scrollTop;
// 此时的开始索引
this.start =
Math.floor(scrollTop / this.itemSize) - this.prevCount >= 0
? Math.floor(scrollTop / this.itemSize) - this.prevCount
: 0;
// 此时的结束索引
this.end = Number(this.start) + Number(this.count) + Number(this.nextCount);
// 此时的偏移量
// console.log('位置', this.start, this.end);
this.startOffset = Number(this.start) * Number(this.itemSize);
}, 0)
}
};
样式
<style lang="scss">
.sickBody {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #f4f4f4;
z-index: 99;
}
.infinite-list-container {
height: 100%;
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;
text-align: center;
}
.infinite-list-item {
padding: 10px;
color: #555;
box-sizing: border-box;
border-bottom: 1px solid #999;
}
.scroll-Y {
height: 100%;
overflow-y: scroll;
}
.height40 {
height: 39px;
line-height: 39px;
width: 100%;
overflow: hidden;
border-bottom: 1px solid #d2d2d2;
}
.positionRelative {
width: 92%;
margin: 0 3%;
position: absolute;
left: 0;
top: 0;
font-size: 32rpx;
padding-bottom: 300rpx;
// height: 100%;
// width: 100%;
}
.parentDom {
position: relative;
}
</style>
总结
总结:
思路很简单:
一、 拿到所有数据应该占用的高度。比如1万条数据,每条占40px,占用高度应为1万*40;
二、拿到展示区域的高度,比如我想展示50条数据,展示高度即为50*40;
三、拿到屏幕滚动的距离,用滚动的距离/行高,获取当前第一行应该展示对应数据的下标值,比如滚动了400px,当前展示的第一条数据的下标应为10
四、截取需要展示的数据:this.testData.slice(this.start, Math.min(this.end, this.testData.length)); 注意前后预留数据,要不然会有空白出现
五、完工
注意事项:一定要脱离标准文档流,给展示区域加定位position:absolute。要不然回流会让你电脑崩溃。里面要不要加节流防抖等细节可以自己看着加。我初期尝试加了节流,加了反而可能造成数据更新会缺少n条,滑动有延迟了,数据被节流了。