思路和原理
先来唠唠实现的思路。要想防止节点过多,就要控制渲染节点的数量。视窗就那么大,渲染十万条数据一次也只能看到窗口里的那点东西,所以我们只用动态渲染一部分即可,当然这会涉及的频繁的操作DOM,但是跟同时渲染几十万上百万数据相比,这点消耗不算什么
提示:list_scroll、list、list_container皆为后面实现代码中的类名,此处用类名指代具体的部分
具体实现如下图
虚拟列表list_scroll
只是为了触发滑动实际上就是个总数据高度相同但是没有任何内容的DIV,所以会设置z-index=-1
把它在页面上遮挡住。
通过获取虚拟列表list_scroll
的滚动高度,然后计算出实际列表list
需要移动的位置来模拟出实际的滚动效果。你能看到的滚动其实是实际列表list
不断的往下挪动形成的,虚拟列表list_scroll
每往上滑动一个节点的高度,实际列表list
就往下移动一个节点的高度
实现代码
<template>
<div>
<div ref="list" class="list_container" @scroll="scrollEvent">
<div class="list_scroll" :style="{ height: listHeight + 'px' }"></div>
<div
class="list"
:style="{ transform: `translateY(${this.startOffset}px)` }"
>
<div class="list_item" v-for="item in visibleData" :key="item">
{{ item }}
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
listData: [], //总数据
//每项高度
itemHeight: 200,
//可视区域高度
screenHeight: 0,
//偏移量
startOffset: 0,
//起始索引
start: 0,
//结束索引
end: null,
};
},
computed: {
//列表总高度
listHeight() {
return this.listData.length * this.itemHeight;
},
//可显示的列表项数
//+2是为了多渲染两个节点,尽量减少快速滑动的时候白屏的影响
visibleCount() {
return Math.ceil(this.screenHeight / this.itemHeight) + 2;
},
//获取真实显示列表数据
visibleData() {
return this.listData.slice(
this.start,
Math.min(this.end, this.listData.length)
);
},
},
created() {
this.onPullDownRefresh();
},
mounted() {
this.screenHeight = this.$refs.list.clientHeight;
this.start = 0;
this.end = this.start + this.visibleCount;
},
methods: {
scrollEvent() {
//当前滚动位置
let scrollTop = this.$refs.list.scrollTop;
if (this.screenHeight + scrollTop == this.listHeight) {
//触底加载
this.onReachBottom();
} else if (scrollTop == 0) {
//触顶刷新
this.onPullDownRefresh();
}
//防止多次计算 只有在需要变化时再计算
if (this.start != Math.floor(scrollTop / this.itemHeight)) {
this.start = Math.floor(scrollTop / this.itemHeight);
this.end = this.start + this.visibleCount;
this.startOffset = scrollTop - (scrollTop % this.itemHeight);
}
},
//下拉刷新(演示demo代码,实际应该是请求分页接口获取数据)
onPullDownRefresh() {
let d = [];
for (let i = 1; i <= 10; i++) {
d.push(i);
}
this.listData = d;
},
//上拉加载(演示demo代码,实际应该是请求分页接口获取数据)
onReachBottom() {
let d = [];
let k = this.listData.length;
for (let i = k + 1; i <= 10 + k; i++) {
d.push(i);
}
this.listData = this.listData.concat(d);
},
},
};
</script>
<style scoped>
.list_container {
height: 700px;
overflow: auto;
position: relative;
-webkit-overflow-scrolling: touch;
}
.list_scroll {
position: absolute;
left: 0;
top: 0;
right: 0;
z-index: -1;
}
.list {
left: 0;
right: 0;
top: 0;
position: absolute;
text-align: center;
}
.list_item {
color: #333;
height: 200px;
line-height: 200px;
box-sizing: border-box;
border-bottom: 1px solid #999;
}
</style>