無限滾動列表,顧名思義,是能夠無限滾動的列表(願意是指那些能夠不斷緩沖加載新數據的列表的)。但是,我們真的需要這樣一個列表嗎?在PC端,瀏覽器的性能其實已經能夠滿足海量dom節點的渲染刷新(筆者經過簡單的測試,1w+的節點並沒有很明顯的卡頓),但是同樣的dom數量在移動端卻不行,在dom結構合理的情況下,能夠保持在2K+的dom數量已是一個很不錯的列表了,但是dom數量再進一步增加就會明顯影響頁面的渲染效率,也正因為此原因,才有了此文關於無限列表的一些思考和探索。
首先,為什么會卡頓?因為dom數量的絕對值上的不斷增多,在后續的交互上(如滾動,局部更新)都會導致頁面渲染性能的下降。
那么,應該如何解決?既然是dom數量過多引起的,最簡單也最有效的方法自然是減少dom數量。在jQuery時代,筆者便見過一種思路(其實本質實現至今也沒有太大的變化):
1、只渲染特定幾屏的數據,將多余的數據緩存在內存中,並不直接實例化到dom tree上,減少dom的絕對數量;
2、監聽滾動事件,在滾動的時候動態的更新dom,讓用戶在視覺上看不出區別;
jQuery的實現由於年代久遠,已經找不太到了,不過筆者在react社區找到了一個比較好的實現:react-virtualized,值得一提的是,它還將我們的滾動場景區分為了viewport內的局部滾動,和基於viewport的滾動,前者相當於在頁面中開辟了一塊獨立的滾動區域,屬於內部滾動(和iscroll的滾動很類似,順帶一提iscroll也給出了iscroll-infinite的解決方案);而后者則把滾動作為了window滾動的一部分(對於移動端而言,在非模態窗的場景下這種滾動更常見,共用一個滾動條也不容易引起用戶的誤操作)。因為筆者主要的使用場景是后者,而iscroll的解決方案雖然也能作用於頁面維度的滾動,但是它需要完全替換scroll事件(相當於頁面沒有了scroll事件,只有touchmove事件;且此種頁面在內部滾動和全局滾動的實現上有不小的難度),所以筆者在無限滾動列表上並沒有使用iscroll的方案。
不過,業內雖然有現成的解決方案固然是好事,但是每種方案都有自己的特性,並不一定都合適,以react-virtualized來說,它的特性在pc上算是一個很有趣也很不錯的解決方案了:
1、它構造一個“足夠大”的容器來再現滾動條的實際數值;
2、它使用絕對定位來不斷跟隨滾動事件,改變元素的位置,幾乎完美還原了正常列表的視覺,而且無論dom再多也不會卡頓;
不過,與此同時,它也有一些不足:
1、因為使用了scroll事件,某種程度上,就注定了在ios上的不足(ios scroll時會阻塞js執行),加上它的緩沖區其實是單向的(雖然這也體現了作者想盡可能節省dom的願景),導致用戶如果上下來回滾動,則很容易看到白屏;
2、由於絕對定位的“小技巧”,它要求在組件渲染之初就必須知道每一行元素的高度,但是這個看起來不起眼的小操作,卻很大的影響了開發的體驗(很不湊巧的筆者使用的無限列表的場景,很多情況下列表元素的高度都不能預先知道。。)
也基於以上原因,雖然react-virtualized是很不錯的解決方案了,但是筆者最終沒有采用。不過,在廣泛借鑒了各大廠的實現之后,筆者發現,手淘列表頁的實現,最簡單也最有效,那么簡單說下實現思路:
1、它引入了分頁的概念,在用戶不斷刷新增加頁面長度的同時,它將若干元素分為一頁;
2、然后在滾動的時候計算當前滾動到了具體的哪一頁,將“多余”(不需要顯示,也不需要作為緩沖顯示)的頁的高度取出,直接賦值在容器上,然后將容器內所有元素置空;
3、當“當前頁”發生變化時,動態將需要顯示“空頁”重新加上元素。
其實這一解決方案也有反復操作dom的性能問題,而且它並不是dom數量優化上的最優解,但是結合筆者的實際使用場景,而且結合考慮到對業務同學的api友好等各方面的,筆者最終也選擇了這一實現,雖然各方面性能不是最好,但是在筆者當前的使用場景中,卻最是有效的。
簡單的小結一下,其實有關無限列表的實現有很多種方案,使用原生scroll事件的痛點在於ios的js阻塞問題以及如何巧妙的設計緩沖區,而使用transform模擬滾動的痛點則是有具體場景的限制和整體的重構成本,兩種方案各有千秋,具體使用還需要看具體的使用場景,所以,聰明的你,告訴我?我們需要無線滾動列表么?