[譯]無盡滾動的復雜度--來自Google大神的拆解

原文地址:https://developers.google.com/web/updates/2016/07/infinite-scroller
原文作者:Surma
譯者:王芃


摘要: 重用你的DOM元素以及刪除那些遠離可視范圍的元素。為延遲顯示的元素使用占位符。這里是一個無盡滾動的演示代碼

無盡滾動在互聯(lián)網(wǎng)上到處都有應(yīng)用。Google Music的藝術(shù)家列表是一個,F(xiàn)acebook的時間線是一個,Tweeter的話題列表也是一個。當你向下滾動,新的內(nèi)容就神奇的“無中生有”了。這是一個得到廣泛贊揚的、非常好的用戶體驗。

在這個無盡滾動背后的技術(shù)挑戰(zhàn)其實比它看上去要難。當你想做正確的事時,你遇到的問題是巨大的。開始時是一些比較簡單的事情,比如在頁面尾部的鏈接是無法點擊的,因為內(nèi)容不斷的把它們“擠”走。但是問題逐漸開始變得越來越難:當用戶將手機從豎屏改為橫屏時你該如何處理 resize 事件?或者當列表過長時你如何避免手機的卡頓?

正確的事

我們認為有充分的理由來實現(xiàn)一個參考設(shè)計:在保證性能的基礎(chǔ)上,以一個可復用的方式來解決這些問題。

我們將會使用3種技術(shù)來達成目標:DOM回收、墓碑和滾動錨定。

我們的demo會是一個類似聊天的窗口,我們可以滾動這些消息列表。首先需要的是一個無盡的消息數(shù)據(jù)源。從技術(shù)角度看,沒有任何一個無盡列表是真正無盡的,但當有足夠的數(shù)據(jù)量填充進去時,它們看上去感覺是無盡的。為簡化問題,我們這里硬編碼了一套消息數(shù)據(jù),隨機的抽取消息、聯(lián)系人和圖片。為了更像網(wǎng)絡(luò)的真實情況,我們?nèi)藶榧尤肓艘恍┭舆t。

image_1b8s8bm77scgbh31ill1qn41h199.png-786kB
image_1b8s8bm77scgbh31ill1qn41h199.png-786kB

DOM 回收

DOM回收是一個未被廣泛使用的技術(shù),它的用途是讓DOM的節(jié)點數(shù)保持在較低的數(shù)值。概括來說,它的機制是利用那些離開視圖區(qū)域的、已經(jīng)創(chuàng)建的DOM元素,而不是新建DOM元素。需要承認的一點是DOM節(jié)點本身并非耗能大戶,但是也不是一點都不消耗性能,每一個節(jié)點都會增加一些額外的內(nèi)存、布局、樣式和繪制。如果一個站點的DOM節(jié)點過多,在低端設(shè)備上會發(fā)現(xiàn)明顯的變慢,如果沒有徹底卡死的話。同樣需要注意的一點是,在一個較大的DOM中每一次重新布局或重新應(yīng)用樣式(在節(jié)點上增加或刪除樣式所觸發(fā)的過程)的系統(tǒng)開銷都會比較昂貴。所以進行DOM回收意味著我們會保持DOM節(jié)點在一個比較低的數(shù)量上,進而加快上面提到的這些處理過程。

第一個障礙是滾動本身。由于我們在任何時刻DOM中只有全部列表項目的一個微小子集,我們需要找到一種方式可以讓瀏覽器正確的反映出理論上應(yīng)該在“那里”的全部列表項目數(shù)量。我們這里用一個 1px * 1px 的”前哨“元素(sentinel),并且應(yīng)用一個變換使得包含“逃兵”列表項目的元素(下圖中的 runway)保持一個理想的高度。我們會把runaway中的每一個元素提升到它們自己的層,保持 runaway 本身是完全空的,沒有背景色,神馬都木有。如果 runaway層不是空的話,是不利于瀏覽器優(yōu)化的。因為我們將不得不在顯卡上存儲一個由成千上萬的像素組成的紋理。這樣做顯然在移動設(shè)備上是不可行的。

當我們進行滾動時,我們會檢查是否viewport是否已經(jīng)足夠接近 runaway 的尾部。如果是的話,我們會通過把 sentinel和viewport中的剩余元素移向 runaway的底部來擴展 runaway,然后用新內(nèi)容渲染這些元素。

向反方向滾動時也類似,但我們無論如何也不會縮小 runaway,原因是我們需要滾動欄的位置保持連續(xù)性。

墓碑(Tombstones)

如之前我們所說,我們會盡量讓數(shù)據(jù)源表現(xiàn)的像現(xiàn)實世界遇到的情況:有網(wǎng)絡(luò)延遲及其它情況。這就意味著如果我們的用戶飛快地滾動,他們會很容易就把我們渲染的有數(shù)據(jù)的項目都甩在身后。如果這種情況發(fā)生時,我們就需要放置一個墓碑條目(占位)在對應(yīng)位置,等到數(shù)據(jù)取到后墓碑條目會被實際內(nèi)容替代。墓碑也會被回收,對于墓碑元素會有一個獨立的可復用DOM元素的池。這樣設(shè)計的原因是,我們希望墓碑元素在被實際數(shù)據(jù)替代時可以有一個漂亮的過渡,而不是出現(xiàn)那種生硬的或者讓人迷失的效果。

墓碑元素
墓碑元素

這里有一個有趣的挑戰(zhàn),那就是真實的條目的高度可能會超過墓碑的高度,因為不同的文本量或者圖片的大小決定了這點。為了解決這個問題,每次當取到數(shù)據(jù)后我們會調(diào)整當前的滾動位置,而且在viewport之上的一個墓碑條目也會被替換。將滾動位置錨定到某一條目而非某一具體的像素位置,這個概念叫做滾動錨定。

滾動錨定

滾動錨定的觸發(fā)時機有兩個:一個是墓碑被替換時,另一個是窗口大小發(fā)生改變時(在設(shè)備發(fā)生翻轉(zhuǎn)時也會發(fā)生)。我們必須要知道在viewport中的最頂部可見元素是什么。由于這個元素可能只是部分可見的,所以我們也需要存儲從頂部元素到viewport頂部的偏移量。

滾動錨定
滾動錨定

這樣的話,當viewport改變大小時、runaway 改變時,我們是可以把場景恢復到一個看起來和原來幾乎一致的樣子。爽就一個字!但是改變大小的視窗意味著每個條目都可能改變了高度,那么我們?nèi)绾文苤涝摪彦^定的內(nèi)容移動多少偏移量呢?我們并不知道!為了搞清楚這點,我們可能不得不把錨定條目之上的元素布局起來,把它們的高度累加在一起。但顯然這樣做會造成改變大小時會有明顯的停頓,我們并不想要這樣的結(jié)果。相反,我們借助于一個假設(shè):在viewport之上的每個元素都是和墓碑等高的。根據(jù)這個假設(shè)來調(diào)整對應(yīng)的滾動位置。當元素滾動進入 runaway 時,我們調(diào)整滾動位置,這樣就有效的把布局工作延遲到真正需要的時候了。

布局

我剛才跳過了一個重要的細節(jié):布局。每次DOM元素的回收通常情況下都會引發(fā)整個 runaway 的重新布局,這會直接影響我們的性能:無法達成每秒60幀的目標。為避免這一點,我們自己承擔了布局的重任,使用了絕對定位的元素。這樣我們可以讓所有 runaway 中的元素感覺上還在占用空間,但其實那里毛都沒有。由于我們自己在操控布局,我們便可以緩存每個元素消失前的位置,在用戶往回滾動時,我們能立刻從緩存中加載正確的元素。

理想情況下,條目應(yīng)該只被重繪一次,那就是當它們被加到DOM時。而且應(yīng)該對于 runaway 中其它條目的增加或刪除完全不受影響。這個是可能的,但是只限于現(xiàn)代瀏覽器。

極致優(yōu)化

最近,Chrome增加了CSS Containment的支持,這個特性允許開發(fā)者告訴瀏覽器某個元素是布局和繪制的邊界。由于我們這里采用的是自己來布局,這是一個很好的可以應(yīng)用 containment 的機會。當我們增加一個元素到 runaway時,我們知道其它條目不應(yīng)該被這個重新布局影響。所以每個條目應(yīng)該設(shè)置一個 contain: layout。我們同樣也不希望影響站點的其它部分,所以 runaway 本身也需要這樣設(shè)置。

另一個優(yōu)化點,我們考慮的是利用IntersectionObservers去檢測用戶是否已經(jīng)滾動了足夠距離,以便于我們決定是否開始回收DOM和加載新數(shù)據(jù)。但是 IntersectionObservers 是為高延遲設(shè)計的,所以我們實際上會“感覺”用了 IntersectionObservers 反而比不用時“響應(yīng)更慢”。在我們當前的實現(xiàn)中滾動事件的處理其實也存在這個問題。也許這個問題的可信度較高的解決方案會是 Houdini’s Compositor Worklet

仍不完美

目前的DOM回收實現(xiàn)方式仍不是完美的,因為我們把所有“滾過”viewport的元素都添加到DOM了,而不是僅僅關(guān)心那些在屏幕上可見的元素。這就意味著,如果你滾動的真的非常非??斓脑?,快到你堆積了大量的布局和繪制工作,瀏覽器已經(jīng)無法跟上的地步時,這時我們可能除了背景什么都看不到了。這當然不是世界末日但是確實是一個可以優(yōu)化的地方。

我們希望你可以看到這個過程:當你想提供一個高性能的有良好用戶體驗的功能時,一個簡單的問題是演變成復雜問題的。隨著“Progressive Web Apps ”逐漸成為移動設(shè)備的一等公民,高性能的良好體驗會變得越來越重要,開發(fā)者也必須持續(xù)的研究使用一些模式來應(yīng)對性能約束。

所有的代碼可以到這里查看,我們已經(jīng)盡力讓代碼有可復用性了,但不會發(fā)布一個npm類庫或其它單獨的項目。這個代碼的主要目的是教學。

慕課網(wǎng) Angular 視頻課上線: http://coding.imooc.com/class/123.html?mc_marking=1fdb7649e8a8143e8b81e221f9621c4a&mc_channel=banner

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標簽?zāi)J的外補...
    _Yfling閱讀 14,190評論 1 92
  • 1. 介紹 瀏覽器可能是最廣泛使用的軟件。本書將介紹瀏覽器的工作原理。我們將看到,當你在地址欄中輸入google....
    康斌閱讀 2,175評論 7 18
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,323評論 25 708
  • 一個人在生活中可能有太多的不順心和太多的誘惑,樂極生悲和痛不欲生的人我們實在看得太多了。雖然僅二十余歲,但...
    紅發(fā)香克斯_閱讀 240評論 0 0
  • 細雨過后,一時的興致?lián)沃愎淦鹆讼嗵幜巳甑男@。一時迎來周圍人數(shù)雙巡視的目光。應(yīng)該是很久沒見過這么充滿童趣的學...
    趙凡一閱讀 423評論 3 5

友情鏈接更多精彩內(nèi)容