Implement Infinte Scroll feat. Vue / Intersection Observer API
三步驟了解並實踐無限滾動加載 feat.Vue / Intersection Observer API
前言
近期工作剛好接觸很多介面開發的部分,其中一個需求實作「無限滾動加載功能」,這是一種常見的加載策略出現在 Facebook、Instagram、Twitter、Pinterest……等主流網站,當使用者滾動到頁面底部時會自動加載更多的資料,不用點擊按鈕就能瀏覽更多的資料,就像是自動版的加載按鈕。
工作中使用的是 Options API 不過這次範例會以 Composition API 來實作,同樣的邏輯也可以套用在任何程式或框架上。
實作
既然叫做滾動「加載」就意味著通常會與第三方索取資料,不過在範例中直接簡化此處的流程,撰寫一個回傳假資料的函式即可,這樣 getCards
函數就可以透過輸入 每頁資料數
與 目前頁數
來產生出對應的卡片資料。
定義問題與執行流程
無限加載不外乎就是自動偵測用戶是否滾動到「無限加載元件」之底部,並觸發加載的動作,於是可以將需求較為詳細定義為以下幾點:
- 如載入後元件仍在視線範圍內,持續載入到視線範圍外
- 如載入後元件在視線範圍外,停止載入
- 超過 x 頁就完全停止載入
為了知道目標元件是否在視線範圍內,可以製作一個通用的元件來偵測並發送事件,也就是接下來要製作的 Observer.vue
元件。
第一步:製作 Observer.vue
偵測元件
傳統要得知 DOM 元素的位置早期通常會使用 getBoundingClientRect()
來取得,不過基於效能、閱讀性以及 API 用途的考量,使用 Intersection Observer API
會是更好的選擇。
Intersection Observer API
自 2019 已經被各大瀏覽器款廣泛支援🔗,其用途主要監測 DOM 元素是否進入或離開另一個 DOM 元素或瀏覽器的視窗範圍內,並且可以透過設定門檻值來觸發對應的事件。
透過埋設一個無內容無樣式的元素在清單的底部並設定:「當與視窗關係發生變化時(離開/進入)」就觸發 Vue 自定義事件執行相關代碼片段。具體來說包裝好的 Observer.vue
元件如下:
程式與概念很簡單,就是透過空 <div>
元素掛載時創建新的 IntersectionObserver
實例,觸發相對應的事件並且在元件銷毀時移除 IntersectionObserver
實例。並且將這個元件引入在無限加載清單底部,這樣就可以透過 @is-in-view
事件來觸發加載的動作。
第二步:設定必備參數
一個無限加載的清單必備的狀態有,可以在開頭初始化定義起來:
maxPage
最大頁數
limitsPerPage
每頁資料數
currentPage
當前頁數
isInView
是否在視線範圍內
並且創建第一批響應式資料,這裡我在卡片中塞入了隨機的 picsum🔗 圖片並且根據 index
作為圖片的 ID(請隨意塞任何你想呈現的資料):
第三步:無限加載邏輯
接著就是在 Observer.vue
觸發事件時透過切換 isInView
的狀態來決定是否繼續執行加載動作,以及只有在 Observer 元件存在於視線中時才會主動觸發加載。
加載過程中特別使用了 lodash
的 throttle
節流函式來控制加載頻率最大 300
毫秒才能觸發一次,是為了:
- 避免加載的內容還沒渲染上畫面,導致瘋狂觸發清單仍未加載滿而反覆加載問題
- 避免用戶頻繁滾動時過度觸發加載請求導致效能問題
關於節流,這裡是我寫過額外的介紹文章:從動圖輕鬆解題:防抖與節流 ,有動畫可以參考看看實際效果。
結語
現在已完成基本的無限加載功能,過程一些加載中、加載失敗的狀態可以根據需求自行調整,這裡就不多做介紹。範例程式可以在 GitHub 上下載🔗。
這是一個很實在的練習題目,特別是在製作更進階的「無限滾動搜尋功能」時碰到了非常多的狀態需要管理,有空或許我會再寫一篇進階版本的文章,並描述我怎麼推導出最終成果。
如果你有更好的想法或是發現任何問題,歡迎在下方留言討論,我會非常的有興趣嘗試或討論看看不同的設計方式,像是:更為 Immutable 的寫法、有限狀態機、State Pattern!
延伸閱讀