上拉加載可行性實(shí)現(xiàn)方案及性能比對(duì)
關(guān)于上拉加載方案的確定,有多種實(shí)現(xiàn)方式,最初考慮監(jiān)聽頁面的滾動(dòng)事件,對(duì)消息列表最后一條消息位置進(jìn)行計(jì)算,判斷當(dāng)其出現(xiàn)在視口中時(shí),請(qǐng)求接口加載新的消息數(shù)據(jù),視圖持續(xù)更新,每次需要選中消息列表的最后一條消息進(jìn)行計(jì)算,處理有些復(fù)雜。綜合考慮各種方案及實(shí)現(xiàn)簡(jiǎn)便性,下面兩種方案可以進(jìn)行討論
方案1
監(jiān)聽頁面滾動(dòng)事件,獲取頁面根元素到視口頂部的距離
x,獲取元素在視口中的高度y,獲取元素的實(shí)際高度z:
- 消息列表在滾動(dòng)過程中未加載到底部時(shí),始終有
x+y<z - 當(dāng)消息列表加載到底部時(shí),有
x+y===z
為了防止頻繁觸發(fā)滾動(dòng)事件的監(jiān)聽事件,對(duì)滾動(dòng)事件進(jìn)行防抖處理,監(jiān)聽事件頻繁觸發(fā)時(shí),每隔著200ms再執(zhí)行一次任務(wù)
[這里有一張圖片]
實(shí)現(xiàn)的代碼也很簡(jiǎn)潔
window.addEventListener('scroll', throttle(scrollEventHandler, 100))
function scrollEventHandler () {
let scrollTop = document.documentElement.scrollTop //元素頂部到視口頂部的距離
let clientHeight = document.documentElement.clientHeight //獲取元素在視口中的高度,包括內(nèi)邊距,不過包括水平滾動(dòng)條/邊框/外邊距
let scrollHeight = document.documentElement.scrollHeight //獲取元素實(shí)際的高度,包括內(nèi)邊距,不過包括水平滾動(dòng)條/邊框/外邊距
if (scrollHeight === scrollTop + clientHeight) {
//調(diào)用請(qǐng)求數(shù)據(jù)接口
}
}
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
- 考慮到監(jiān)聽消息列表的最后一條消息并計(jì)算其是否出現(xiàn)在視口,需要在每次
dom更新完畢后重新選擇最后一條消息進(jìn)行計(jì)算其是否出現(xiàn)在視口中,因此想到一種比較便捷的實(shí)現(xiàn)方法。- 封裝一個(gè)公用的底部bar組件,始終置于消息列表尾部,根據(jù)
Intersection Observer方法判斷其是否出現(xiàn)在視口中,當(dāng)其出現(xiàn)在視口中,表示當(dāng)前消息頁面已經(jīng)加載到頁面底部。
[這里也有一張圖片]
- 這個(gè)種實(shí)現(xiàn)方案不需要監(jiān)聽頁面的滾動(dòng)事件,在實(shí)現(xiàn)上也比較方便??紤]到
api的兼容性,以及鎖屏業(yè)務(wù)的實(shí)際場(chǎng)景,不需要再引入額外的polyfill文件
封裝的公用底bar組件,可以傳入當(dāng)?shù)?code>bar出現(xiàn)在視口中后相應(yīng)的加載事件,在消息加載完畢后停止對(duì)底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">網(wǎng)絡(luò)信號(hào)差,請(qǐng)重試</span>
<span v-if="status==`no-connection`" class="bottom-bar-txt">無網(wǎng)絡(luò)連接,請(qǐng)?jiān)O(shè)置網(wǎng)絡(luò)</span>
<span v-if="status==`no-service`" class="bottom-bar-txt">服務(wù)器異常,請(qǐng)上劃重試</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前調(diào)用,初始化某些屬性值,渲染成視圖
},
computed(){
},
mounted() {
//模板渲染成html后調(diào)用,初始化頁面完成后,對(duì)htmldom節(jié)點(diǎn)進(jìn)行一些操作
},
methods: {
}
}
</script>
無限滾動(dòng)時(shí),最好在頁面底部有一個(gè)頁尾欄(又稱sentinels)。一旦頁尾欄可見,就表示用戶到達(dá)了頁面底部,從而加載新的條目放在頁尾欄前面。這樣做的好處是,不需要再一次調(diào)用observe()方法,現(xiàn)有的IntersectionObserver可以保持使用。