不管是 web 端還是移動端,信息流都是現在很流行的信息展示方式。信息流經常搭配自動加載一起使用以獲得更好的使用體驗。
最近在使用 Vue 開發過程中也遇到了首頁信息流自動加載的需求。大致了解了一下幾個滾動自動加載組件,發現多數都是把內容放在在一個單獨的滾動容器內;但我遇到的需求是整個頁面的滾動(博客列表首頁那種),不是限制在容器內,不太符合。把整個頁面放進滾動容器明顯很奇怪,只是為了一個簡單的下拉加載不值當。所以參考網上的一些介紹實現了一個幾十行的簡單小組件 ButtomDetector
來實現這個功能,同時也方便在多個需要到底自動加載的頁面中進行復用。
實現原理
實現原理非常簡單:
- 監聽頁面的滾動事件
- 在觸發滾動時,調用 DOM api 來獲取頁面的滾動狀態
- 如果已經滾動到底,拋出事件供頁面使用
JS 事件監聽
// 監聽 scroll 事件,綁定回調函數
window.addEventListener('scroll', this.listenBottomOut)
// 取消事件監聽
window.removeEventListener('scroll', this.listenBottomOut, false)
頁面滾動狀態
DOM 提供了獲取頁面元素滾動狀態的相關屬性,這里主要用到以下三個:scrollHeight
滾動內容高度,scrollTop
滾動內容頂部離顯示區域的距離,clientHeight
顯示區域高度。如下圖所示。
因此,判斷滾動到底就很簡單了:
if (scrollTop + clientHeight >= scrollHeight - delta) {
// emit bottom event
}
其中 delta
的作用是可以在快到底端(例如還有50px)時提前進行加載,避免拉到底后等待加載的這段時間。
一些細節問題
重復拋出事件
因為監聽的是 scroll 事件,並且使用 delta 做了一點提前加載,因此可能會觸發多次觸底事件(距離底部48px, 30px, 12px... 都符合判斷條件),造成頁面重復多次加載數據。因此選擇在組件中增加一個 loadingMore
屬性,表示父頁面正在加載數據,此時不再繼續拋出事件;當頁面完成加載后,將 loadingMore
重置為 false 以繼續監聽觸底。
沒有更多數據
在信息流已經到底,沒有更多數據的時候,觸底事件就沒有作用了。當然這個判斷也可以在父頁面中進行,當沒有更多數據時不再繼續處理觸底事件;不過為了方便頁面中的加載數據方法(不用每次都單獨判斷是否有更多),給組件增加一個 noMore
參數,此時不再拋出觸底事件。
代碼實現
bottomDetector 組件
<template>
<div style="text-align: center">
<div v-if="loadingMore">加載中</div>
<div v-if="noMore">沒有更多了</div>
</div>
</template>
<script>
export default {
props: {
loadingMore: {
type: Boolean,
required: true,
},
distance: {
type: Number,
default: 50,
},
noMore: {
type: Boolean,
default: false,
},
},
mounted() {
window.addEventListener('scroll', this.listenBottomOut)
this.element = document.documentElement
},
destroyed() {
window.removeEventListener('scroll', this.listenBottomOut, false)
},
data() {
return {
element: null,
}
},
methods: {
listenBottomOut() {
if (this.noMore || this.loadingMore) return
let scrollTop = this.element.scrollTop || document.body.scrollTop
let clientHeight = this.element.clientHeight
let scrollHeight = this.element.scrollHeight
if (scrollTop + clientHeight >= scrollHeight - this.distance) {
this.$emit('arriveBottom')
}
},
},
}
</script>
頁面使用
從觸底功能上來說,detector 組件放在頁面中的任何位置都可以。放在信息流下方的話還可以順便做加載動畫和信息到底的提示信息。
<div class="infos">
<div v-for="item in infos" :key="item.id">
<!-- info -->
</div>
</div>
<bottom-detector
:loadingMore="infosLoadingMore"
:noMore="infos.length >= infoTotal"
@arriveBottom="getMoreInfo"
/>
</div>
getMoreInfo() {
this.infosLoadingMore = true
// loading more info
this.infosLoadingMore = false
},
結語
以上是簡單的 Vue 頁面觸底加載組件的原理及實現,希望對你有所幫助,歡迎在評論區進行討論或指正。
監聽事件函數部分參考了博客:觸底加載更多(原理 + 在vue中的使用)。