🌟 記一次「 無限滾動 」列表優化

背景

長列表優化, 是頁面性能優化中的一個比較常見的問題,也是面試中的常客。面試

恰好最近在的項目中, 遇到了一個長列表的性能問題,試過多種方案, 最後得以解決。chrome

今天就給你們分享一下。瀏覽器

正文

場景描述

用戶須要批量修改 Product中 sku 的 映射關係,能夠選擇的 Product 的 數量不限性能優化

每一條sku 對應以下結構:antd

由於能夠選擇的sku數量是不限的, 又不能分頁, 只能作到一個列表裏。dom

因而, 長列表出現了。工具

剛開始的方案是作一個虛擬列表性能

具體就是經過監聽sroll事件,每次滾動後計算通常元素位置(top和height)測試

而後,經過渲染三屏的方式,把一段數據渲染到頁面上。優化

數據量很少的時候, 沒什麼問題。

當選擇幾百上千條sku 的時候, 快速滑動, 就開始出現卡頓。

如圖所示:

快速滾動出現空白

做爲對比,看一下優化後的效果:

優化以後

問題定位

在chrome調試工具下,邊拖動列表邊觀察dom的變化。

發現,dom的卸載/掛載/更新的狀況都出奇地慢,鼠標已經停下來,能明顯感受到過一會dom才裝載完成,因此極可能是dom的渲染性能問題。

定位到渲染性能有問題的dom身上,即每個 Item(renderFakeTable)。

使用普通文本代替Item,在一樣多數量的列表狀況下,簡單的dom明顯會順暢不少,可是,仍然會出現空白問題。

繼續觀察renderFakeTable中的每個元素(能夠借用devTools Profiler)。

最簡單粗暴的方式就是去除某一類的組件,而後經過不斷自測的方式,找出最有可能影響渲染效率的元素:

SearchSelect(基於antd的Select封裝的一個業務組件)。

因此,影響渲染性能的元素極可能就是它。

渲染性能

除了組件的問題,還有多是渲染的問題。

首先,原來無限滾動的邏輯就是基於scroll事件,經過不斷滾動觸發的回調,從新計算渲染到頁面上的區間。

其次,爲了動態調整可視區域的元素,使用了MutationObserver。

致使空白問題則會有這幾種可能:

  1. 沒加防抖,頻繁渲染帶來性能消耗
  2. scroll 和 MutationObserver 相繼執行了渲染,致使dom出現了跳動的現象。
  3. 預留的元素個數,viewPrepareCount過小了,致使拖動太快時,後面或前面都沒有多餘的可見元素
  4. 沒有開啓GPU加速,應該使用transform代替top來定位到正確位置

不幸運的是,以上的可能都一一排除後,發現幾乎沒有啥提高。

其實,在第二點縮小範圍時,應該意識到,空白問題/拖動不流暢均是由於渲染性能低下致使的

測試驗證

1. 虛擬列表 rc-virtual-list

爲了驗證是Select 組件的問題,基於:

rc-virtual-list

作了一個在線 demo :

在線地址:https://codesandbox.io/s/opti...

動態演示:

這裏渲染了1000 條記錄, 每條記錄裏有5個select;

默認使用的是 antd Select, 幾乎拉不動;

切換到原生select以後, 如絲般順滑。

由此能夠肯定,卡頓是 Slect 組件引發的。

因此要減小渲染成本:

  1. 減小本身的父組件渲染成本,React.memo/React.useMemo/React.useCallback.
  2. 減小Select渲染成本(比較麻煩,並且效果不明顯。通過自測,僅僅是使用一個基礎的Select,rc無限滾動的狀況下一樣發生了卡頓)

2. 下拉懶加載

基於 Intersection Observer 實現一個 下拉懶加載。

利用 Intersection Observer 實現:

在列表的底部(也多是底部偏上的某個位置)插入一個observer-dom元素.

經過Observer來觀測其是否在可視區域中,若是在,那麼就往下加載更多的內容:

初始狀態時,列表會多渲染幾條數據(兩屏數據),observer-dom元素一直被頂到底部.

用戶往下滾動時,observer-dom元素「出現」在用戶視野。

每次多加載一屏的數據,循環如此,直到整個列表都渲染到頁面上。

在線demo:
https://codesandbox.io/s/gund...

動態演示:

選擇方案

  1. 要麼接受使用rc無限滾動的不夠流暢;
  2. 要麼使用 Intersection Observer 實現一個下拉懶加載的無限滾動效果

最終採用下拉懶加載。


總結

一般,無限滾動的方案能夠分爲兩種:

1. 虛擬長列表

  • 優勢:能夠保證渲染在頁面上的dom元素儘量少
  • 缺點:若是沒有特殊處理(好比rc或鎖定滾動區域),快速滾動時,基本都會有閃動的狀況(也就是本次的空白問題)

2. 下拉懶加載

  • 優勢:防止用戶快速拖動的出現閃動問題。再經過加一個loading效果,幫助優化體驗
  • 缺點:當用戶把列表拉到底,整個列表都會被渲染到頁面上

在選擇虛擬長列表or下拉懶加載之間的取捨時,能夠參考:

若是閃動問題能夠接受(組件渲染沒有太大性能問題),並且對dom數量要求很嚴格,那麼選擇虛擬長列表會更好。

若是閃動問題不能接受,而最終的dom數量可以接受,那麼選擇下拉藍加載會更好。

不管是選擇虛擬長列表or下拉懶加載,在使用監聽scroll事件或者Intersetion Observer API之間的取捨時,能夠參考:

  • scroll的事件回調會在主線程中被成千上萬次調用,儘管加了防抖
  • scroll的方式,須要不斷記錄scrollTop和元素高度

而使用Intersetion Observer API,上述幾點的計算就能夠省略了,優化工做交給了瀏覽器。
若是不考慮IE 等, 它是一個不錯的選擇。

內容就這麼多, 但願對你們有所啓發。

若有錯誤, 歡迎指正, 謝謝。

相關文章
相關標籤/搜索