從0開始寫一個虛擬滾動元件

從0開始寫一個虛擬滾動元件

如果一個頁面有1W+條資料,該怎麼渲染比較好。不管是在我們的實際專案開發中還是在面試的過程中都會遇到類似的問題。相信很多同學會想到分頁。當然這也是最傳統也是最保底的解決方案了。

如果有開發過electron相關的專案,很多資料都會儲存在本地sqlite資料庫中,有些列表需要一次性渲染出來,無法使用使用分頁方案。因此虛擬滾動技術是一個特別合適的解決方案。

虛擬滾動本身就是技術的一個簡單的應用,並沒有什麼高深的邏輯

虛擬滾動的基本原理

基本原理就是我們只渲染一個長列表的中間在可視區部分的資料,其它的資料不參與渲染。

是不是感覺很抽象?其實網上已經有各路大神給出他們的實現思路。slice擷取陣列得到我們要渲染的真實的節點資料。頂部和底部分別用padding撐開。在列表的滾動的過程中不斷地計算擷取資料的位置即可。

我的思路

資料的每一項dom節點都採用絕對定位脫離文件流,然後透過定位的方式計算每一個節點的位置。最後也是透過滾動的距離計算出偏移量,再拿v-if進行判斷渲染與否。先來看效果

從0開始寫一個虛擬滾動元件

再來看下基本的dom結構,一目瞭然。所有脫離文件流的節點的父級需要計算高度,撐起列表的捲軸。

從0開始寫一個虛擬滾動元件

最簡單的dom結構

接下來最核心的問題就是要在handleScroll方法中計算start偏移量了。可能大家覺得這一步很簡單了。

handleScroll(e){ const { scrollTop } = e。target; this。start = scrollTop / 50; //50是列表每一項的高度}

完美,搞定。

基本到這,基本功能完成。

but

有問題,稍微對程式碼比較敏感的開發者看到這裡,都會疑惑,我們做虛擬滾動,本身就是在做效能最佳化。這裡不能最佳化一下麼?

問題是什麼

scroll事件觸發頻率是非常頻繁的。不停地去計算start值,實際浪費了很多無效的CPU資源,那問題又來了,什麼時候需要去計算start呢?可能大家會想到用節流,但是很遺憾,這裡不適合用。

為什麼呢?

因為列表滾動是使用者的一個主動行為。使用者有可能快速拉動捲軸,快速滾屏操作。我們計算start的頻率也應該越高。不然列表的頂部或者底部會出現白底。

最佳化思路僅是我個人對於元件的思考,如果有更好的方案歡迎大家評論區補充。

使用者在滾動列表的過程中,我會時判斷捲軸的滾動方向。無非兩種情況:

捲軸向下。這時候,我會去獲取到列表最後一項的dom節點。判斷這個節點距離底部的距離,如果距離小於一個固定值時,開始計算start的值。

捲軸向上。同理。這時候,我會去獲取到列表第一項的dom節點。判斷第一個節點距離頂部的距離。如果大於一個固定值時,開始計算start值。

此時問題又來了: 怎麼判斷捲軸的滾動方向呢?

這裡稍等有一些經驗的同學應該很容易想到,維護一個上一次的scrollTop值。兩個次的差值如果大於0則捲軸向上,反之則反。

核心程式碼 :

從0開始寫一個虛擬滾動元件

判斷捲軸的滾動方向

以上就是我針對虛擬滾動技術的理解以及實踐,各路大神如果有更好的方案歡迎同我交流。

注:此技術已經應用在我的真實的專案開發中。

可能有人問我為什麼不用第三方框虛擬元件呢?

因為我們具體的真實專案中,可能存在各種變化,無力去熟悉第三方元件的原始碼,也不一定全能吃透。而我們自己研發的元件,原始碼熟悉,擴充套件起來也是很方便。

寫在最後

關於技術,希望大家有自己的思考。此元件我只公開了部分的原始碼。大家可以根據實際需求自行DIY,武裝自己。

有什麼問題可以隨時在評論區交流哦。