日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

一个简洁、有趣的无限下拉方案

發布時間:2024/9/15 编程问答 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一个简洁、有趣的无限下拉方案 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

作者:網易云音樂前端團隊

https://juejin.im/post/5de5baf2518825235b095cbe

本文主旨

長列表渲染、無限下拉也算是前端開發老生常談的問題之一了,本文將介紹一種簡潔、巧妙、高效的方式來實現。話不多說,看下圖,也許你可以發現什么?

不知你是否從上面這張圖中注意到了什么,比如只是渲染了可視區域的部分 DOM?,滾動過程中只是外層容器的 padding 在改變?

前一點很好理解,我們考慮到性能,不可能將一個長列表(甚至是一個無限下拉列表)的所有列表元素都進行渲染;而后一點,則是本文所介紹方案的核心之一!

不賣關子,提前告訴你該方案的要素就是兩個:

  • Interp Observer

  • padding

說明了要素,也許你可以嘗試著開始思考,看你是否能猜到具體的實現方案。

方案介紹

Interp Observer

基本概念

一直以來,檢測元素的可視狀態或者兩個元素的相對可視狀態都不是件容易事。傳統的各種方案不但復雜,而且性能成本很高,比如需要監聽滾動事件,然后查詢 DOM , 獲取元素高度、位置,計算距離視窗高度等等。

這就是 Interp Observer 要解決的問題。它為開發人員提供一種便捷的新方法來異步查詢元素相對于其他元素或視窗的位置,消除了昂貴的 DOM 查詢和樣式讀取成本。

兼容性

主要在 Safari 上兼容性較差,需要 12.2 及以上才兼容,不過還好,有 polyfill 可食用。

一些應用場景

  • 頁面滾動時的懶加載實現。

  • 無限下拉(本文的實現)。

  • 監測某些廣告元素的曝光情況來做相關數據統計。

  • 監測用戶的滾動行為是否到達了目標位置來實現一些交互邏輯(比如視頻元素滾動到隱藏位置時暫停播放)。

padding 方案實現

基本了解 Interp Observer 之后,接下來就看下如何用 Interp Observer + padding 來實現無限下拉。

先概覽下總體思路:

  • 監聽一個固定長度列表的首尾元素是否進入視窗;

  • 更新當前頁面內渲染的第一個元素對應的序號;

  • 根據上述序號,獲取目標數據元素,列表內容重新渲染成對應內容;

  • 容器 padding 調整,模擬滾動實現。

核心:利用父元素的 padding 去填充隨著無限下拉而本該有的、越來越多的 DOM 元素,僅僅保留視窗區域上下一定數量的 DOM 元素來進行數據渲染。

1、監聽一個固定長度列表的首尾元素是否進入視窗

// 觀察者創建 this.observer = new InterpObserver(callback, options);// 觀察列表第一個以及最后一個元素 this.observer.observe(this.firstItem); this.observer.observe(this.lastItem); 復制代碼

我們以在頁面中渲染固定的 20 個列表元素為例,我們對第一個元素和最后一個元素,用 Interp Observer 進行觀察,當他們其中一個重新進入視窗時,callback 函數就會觸發:

const callback = (entries) => {entries.forEach((entry) => {if (entry.target.id === firstItemId) {// 當第一個元素進入視窗} else if (entry.target.id === lastItemId) {// 當最后一個元素進入視窗}}); }; 復制代碼

2、更新當前頁面渲染的第一個元素對應的序號 (firstIndex)

拿具體例子來說明,我們用一個數組來維護需要渲染到頁面中的數據。數組的長度會隨著不斷請求新的數據而不斷變大,而渲染的始終是其中一定數量的元素,比如 20 個。那么:

  • 1、最開始渲染的是數組中序號為 0 - 19 的元素,即此時對應的 firstIndex 為 0;

  • 2、當序號為 19 的元素(即上一步的 lastItem )進入視窗時,我們就會往后渲染 10 個元素,即渲染序號為 10 - 29 的元素,那么此時的 firstIndex 為 10;

  • 3、下一次就是,當序號為 29 的元素進入視窗時,繼續往后渲染 10個元素,即渲染序號為 20 - 39 的元素,那么此時的 firstIndex 為 20,以此類推。。。

// 我們對原先的 firstIndex 做了緩存 const { currentIndex } = this.domDataCache;// 以全部容器內所有元素的一半作為每一次渲染的增量 const increment = Math.floor(this.listSize / 2);let firstIndex;if (isScrollDown) {// 向下滾動時序號增加firstIndex = currentIndex + increment; } else {// 向上滾動時序號減少firstIndex = currentIndex - increment; } 復制代碼

總體來說,更新 firstIndex,是為了根據頁面的滾動情況,知道接下來哪些數據應該被獲取、渲染。

3、根據上述序號,獲取對應數據元素,列表重新渲染成新的內容

const renderFunction = (firstIndex) => {// offset = firstIndex, limit = 10 => getData// getData Done => new dataItems => render DOM}; 復制代碼

這一部分就是根據 firstIndex 查詢數據,然后將目標數據渲染到頁面上即可。

4、padding 調整,模擬滾動實現

既然數據的更新以及 DOM 元素的更新我們已經實現了,那么無限下拉的效果以及滾動的體驗,我們要如何實現呢?

想象一下,拋開一切,最原始最直接最粗暴的方式無非就是我們再又獲取了 10 個新的數據元素之后,再塞 10 個新的 DOM 元素到頁面中去來渲染這些數據。

但此時,對比上面這個粗暴的方案,我們的方案是:這 10個新的數據元素,我們用原來已有的 DOM 元素去渲染,替換掉已經離開視窗、不可見的數據元素;而本該由更多 DOM 元素進一步撐開容器高度的部分,我們用 padding 填充來模擬實現。

  • 向下滾動

// padding的增量 = 每一個item的高度 x 新的數據項的數目 const remPaddingsVal = itemHeight * (Math.floor(this.listSize / 2));if (isScrollDown) {// paddingTop新增,填充頂部位置newCurrentPaddingTop = currentPaddingTop + remPaddingsVal;if (currentPaddingBottom === 0) {newCurrentPaddingBottom = 0;} else {// 如果原來有paddingBottom則減去,會有滾動到底部的元素進行替代newCurrentPaddingBottom = currentPaddingBottom - remPaddingsVal;} } 復制代碼


  • 向上滾動

// padding的增量 = 每一個item的高度 x 新的數據項的數目 const remPaddingsVal = itemHeight * (Math.floor(this.listSize / 2));if (!isScrollDown) {// paddingBottom新增,填充底部位置newCurrentPaddingBottom = currentPaddingBottom + remPaddingsVal;if (currentPaddingTop === 0) {newCurrentPaddingTop = 0;} else {// 如果原來有paddingTop則減去,會有滾動到頂部的元素進行替代newCurrentPaddingTop = currentPaddingTop - remPaddingsVal;} } 復制代碼
  • 最后是 padding 設置更新以及相關緩存數據更新

// 容器padding重新設置 this.updateContainerPadding({newCurrentPaddingBottom,newCurrentPaddingTop })// DOM元素相關數據緩存更新 this.updateDomDataCache({currentPaddingTop: newCurrentPaddingTop,currentPaddingBottom: newCurrentPaddingBottom }); 復制代碼

思考總結

方案總結:

利用 Interp Observer 來監測相關元素的滾動位置,異步監聽,盡可能得減少 DOM 操作,觸發回調,然后去獲取新的數據來更新頁面元素,并且用調整容器 padding 來替代了本該越來越多的 DOM 元素,最終實現列表滾動、無限下拉。

相關方案的對比

這里和較為有名的庫 - iScroll 實現的無限下拉方案進行一個基本的對比,對比之前先說明下 iScroll infinite 的實現概要:

  • iScroll 通過對傳統滾動事件的監聽,獲取滾動距離,然后:

  • 設置父元素的 translate 來實現整體內容的上移(下移);

  • 再基于這個滾動距離進行相應計算,得知相應子元素已經被滾動到視窗外,并且判斷是否應該將這些離開視窗的子元素移動到末尾,從而再對它們進行 translate 的設置來移動到末尾。這就像是一個循環隊列一樣,隨著滾動的進行,頂部元素先出視窗,但又將移動到末尾,從而實現無限下拉。

    • 相關對比:

      • 實現對比:一個是 Interp Observer 的監聽,來通知子元素離開視窗,只要定量設置父元素 padding 就行;另一個是對傳統滾動事件的監聽,滾動距離的獲取,再進行一系列計算,去設置父元素以及子元素的 translate。顯而易見,前者看起來更加簡潔明了一些。

      • 性能對比:我知道說到對比,你腦海中肯定一下子會想到性能問題。其實性能對比的關鍵就是 Interp Observer。因為單就 padding 設置還是 translate 設置,性能方面的差距是甚小的,只是個人感覺 padding 會簡潔些?而 Interp Observer 其實抽離了所有滾動層面的相關邏輯,你不再需要對滾動距離等相應 DOM 屬性進行獲取,也不再需要進行一系列滾動距離相關的復雜計算,并且同步的滾動事件觸發變成異步的,你也不再需要另外去做防抖之類的邏輯,這在性能方面還是有所提升的。

      存在的缺陷:

      • padding 的計算依賴列表項固定的高度。

      • 這是一個同步渲染的方案,也就是目前容器 padding 的計算調整,無法計算異步獲取的數據,只跟用戶的滾動行為有關。這看起來與實際業務場景有些不符。解決思路:

        • 思路 1、利用 Skeleton Screen Loading 來同步渲染數據元素,不受數據異步獲取的影響。即在數據請求還未完成時,先使用一些圖片進行占位,待內容加載完成之后再進行替換。

        • 思路 2、滾動到目標位置,阻塞容器 padding 的設置(即無限下拉的發生)直至數據請求完畢,用 loading gif 提示用戶加載狀態,但這個方案相對復雜,你需要全面考慮用戶難以預測的滾動行為來設置容器的 padding。

      延伸拓展

      • 請大家思考一下,無限下拉有了,那么無限上拉基于這種方案要如何調整實現呢?

      • 如果將 Interp Observer 用到 iScroll 里面去,原有方案可以怎樣優化?

      代碼實現

      • 完整代碼實現參考(https://github.com/Guohjia/listScroll)

      參考文章

      • Interp Observer API

      • InterpObserver’s Coming into View

      • Infinite Scroll’ing the right way

      END 最后說個題外話,相信大家都知道視頻號了,隨著灰度范圍擴大,越來越多的小伙伴都開通了視頻號。小詹也開通了一個視頻號,會分享互聯網那些事、讀書心得與副業經驗,歡迎掃碼關注,和小詹一起向上生長!「沒有開通發布權限的盡量多互動,提升活躍度可以更快開通哦」(聽我一分鐘,生活更輕松)(掃碼回復 1024 即可領取IT資料包)

    總結

    以上是生活随笔為你收集整理的一个简洁、有趣的无限下拉方案的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。