上拉加载可行性实现方案及性能比对
关于上拉加载方案的确定,有多种实现方式,最初考虑监听页面的滚动事件,对消息列表最后一条消息位置进行计算,判断当其出现在视口中时,请求接口加载新的消息数据,视图持续更新,每次需要选中消息列表的最后一条消息进行计算,处理有些复杂。综合考虑各种方案及实现简便性,下面两种方案可以进行讨论
方案1
监听页面滚动事件,获取页面根元素到视口顶部的距离
x
,获取元素在视口中的高度y
,获取元素的实际高度z:
- 消息列表在滚动过程中未加载到底部时,始终有
x+y<z
- 当消息列表加载到底部时,有
x+y===z
为了防止频繁触发滚动事件的监听事件,对滚动事件进行防抖处理,监听事件频繁触发时,每隔着200ms
再执行一次任务
[这里有一张图片]
实现的代码也很简洁
window.addEventListener('scroll', throttle(scrollEventHandler, 100))
function scrollEventHandler () {
let scrollTop = document.documentElement.scrollTop //元素顶部到视口顶部的距离
let clientHeight = document.documentElement.clientHeight //获取元素在视口中的高度,包括内边距,不过包括水平滚动条/边框/外边距
let scrollHeight = document.documentElement.scrollHeight //获取元素实际的高度,包括内边距,不过包括水平滚动条/边框/外边距
if (scrollHeight === scrollTop + clientHeight) {
//调用请求数据接口
}
}
function debounce(fn, delay) {
let timer = delay
return function () {
let context = this
let args = arguments
clearTimeout(timer)
timer = setTimeout(function() {
fn.apply(context, args)
}, delay)
}
}
方案2
- 考虑到监听消息列表的最后一条消息并计算其是否出现在视口,需要在每次
dom
更新完毕后重新选择最后一条消息进行计算其是否出现在视口中,因此想到一种比较便捷的实现方法。- 封装一个公用的底部bar组件,始终置于消息列表尾部,根据
Intersection Observer
方法判断其是否出现在视口中,当其出现在视口中,表示当前消息页面已经加载到页面底部。
[这里也有一张图片]
- 这个种实现方案不需要监听页面的滚动事件,在实现上也比较方便。考虑到
api
的兼容性,以及锁屏业务的实际场景,不需要再引入额外的polyfill
文件
封装的公用底bar
组件,可以传入当底bar
出现在视口中后相应的加载事件,在消息加载完毕后停止对底bar
元素的观察
<template>
<div v-if="isShow" class="bottom-bar">
<div v-if="status==`loading`" class="bottom-bar-loading">
<i class="bottom-bar-loading-icon"/>
<span class="bottom-bar-txt loading-txt">正在加载...</span>
</div>
<span v-if="status==`completed`" class="bottom-bar-txt">已显示全部消息</span>
<span v-if="status==`poor-network`" class="bottom-bar-txt">网络信号差,请重试</span>
<span v-if="status==`no-connection`" class="bottom-bar-txt">无网络连接,请设置网络</span>
<span v-if="status==`no-service`" class="bottom-bar-txt">服务器异常,请上划重试</span>
</div>
</template>
<script>
export default {
name: 'BottomBar',
props: {
isShow: {
type: Boolean,
default: true
},
status: {
type: String,
default: 'loading'
},
loadMethod: {
type: Function,
required: true
},
observerConfig: {
type: Object,
required: true,
default: {
threshold: 0.5
}
}
},
computed: {
observer() {
return new IntersectionObserver(([entry]) => {
if(entry && this.isShow && entry.isIntersecting) {
this.loadMethod()
}
}, this.observerConfig)
}
},
mounted() {
this.observer.observe(this.$el)
},
methods: {
unobserver: function() {
this.observer.unobserve(this.$el)
}
}
}
</script>
父组件:
<template>
<BottomBar :status="status" :load-method="getLikes" ref="bar"/>
</template>
<script>
import BottomBar from './BottomBar'
export default {
components: {
BottomBar
},
data() {
return {
status: 'loading'
}
},
created() {
//模板渲染成html前调用,初始化某些属性值,渲染成视图
},
computed(){
},
mounted() {
//模板渲染成html后调用,初始化页面完成后,对htmldom节点进行一些操作
},
methods: {
}
}
</script>
无限滚动时,最好在页面底部有一个页尾栏(又称sentinels)。一旦页尾栏可见,就表示用户到达了页面底部,从而加载新的条目放在页尾栏前面。这样做的好处是,不需要再一次调用observe()
方法,现有的IntersectionObserver
可以保持使用。