歡迎光臨
每天分享高質量文章

頁面滾動效能

介紹

滾動乍看起來和效能毫無關係。畢竟,你的內容都有了樣式,靜態資源也已開始載入或已經載入完畢,那我們為什麼會突然對滾動感興趣了呢?原因很簡單,一旦開始滾動,瀏覽器就需要把你的網站或應用繪製到螢幕上。這就意味著,我們可以最小化瀏覽器的繪製工作,將頁面效能最大化。

當使用者使用你的應用時,擁有平滑滾動經常是被忽視但卻是使用者體驗中至關重要的部分。當滾動表現正常時,使用者就會感覺應用十分流暢,令人愉悅,而不是笨重不自然。

滾動的細節

讓我們來瞧瞧在滾動時到底發生了什麼。在理解這個問題之前,我們先簡要的介紹下瀏覽器是如何向螢幕繪製內容的。這一切都是從 DOM 樹(本質上就是頁面中的所有元素)開始的。瀏覽器先檢查擁有了樣式的 DOM,然後找到那些它認為在滾動時不會改變的元素,然後將這些元素分組並對它們拍照(就是層)。這些層都需要繪製並柵格化為一個紋理,然後混合在一起成為你在螢幕上看到的影象。

如果你對 Chrome 的渲染細節感興趣,不妨看看這篇總結文章。

當滾動頁面時,瀏覽器很可能需要在這些層(有時稱為複合層,即 compositor layers)上繪製一些畫素點。透過將內容分組為層後,當某個特定的層內發生改變,我們只需更新該層的紋理,並且只繪製和柵格化渲染層的紋理中被損壞的部分,而不必繪製所有內容。很顯然,如果你在滾動過程中移動了某些內容,就像視差網站那樣,你就可能損壞一大片區域,很大程度上會涉及多個層,結果就是會造成耗費嚴重的繪製工作。

簡而言之,越少繪製越好。

診斷你的繪製

理論知識講了一籮筐,還是看下實際應用吧。這是一個演示頁面,你可以啟動 Chrome 的開發者工具來觀察它。如果你開啟時間軸面板,設定成 frame 樣式,點選記錄(record)按鈕並開始滾動頁面,你將會看到一堆綠柱。我錄製了一段影片,演示瞭如何操作和應該看到的結果:

影片地址:https://www.youtube.com/watch?v=KCtOt9OXvAM

在時間軸下的串列中,你會看到全部繪製堆的記錄。它們就是 Chrome 在為頁面的混合層繪製與柵格化紋理。(在繪製工作結束後,你可能會看到一些複合層的記錄,它們就是更新後的層,為顯示在螢幕上做好了複合的準備。)

Chrome 開發者工具中的繪製記錄

緊挨著繪製記錄的是繪製的區域,你把滑鼠放到上面,Chrome 會高亮頁面中重繪的區域。另一種檢視重繪區域的方法是在 Chrome 開發者工具的設定頁面(點選工具右上角的小齒輪進入)開啟 “Show paint rectangles”。這是用來瞭解滾動時頁面表現的第一個指示器。提示:你應該盡可能的使繪製區域最小化。

當側邊欄顯示時,你會看到繪製的區域就是整個螢幕,導致效能很差。造成這種情況的原因是 Chrome 對受損區域做了並集。本例中,在內容區上方,寬度為 100% 的帶狀區域進入視野中,而高度為 100% 的側邊欄也被繪製進了同樣的複合層內,這些區域合併在一起就成了一個 100% 寬和 100% 高的區域。如果你用頁面上方的核取方塊來禁用側邊欄,再次滾動你會發現重繪的僅僅是頂部的帶狀區域,響應速度更快了。

如果你想看個真實體子,就開啟 Google+,將側導航欄從 position: fixed 修改為 position: absolute。當然了,這就改變了網站的行為,但要點在於,你能看到一個透過切換樣式來達到效能提升的真實體子。可能當你看到這之後的第一反應就是決定以後再也不用 position:fixed 了,但這與所在環境和需求有關。任何東西都有它適用的地方,重要的是能夠測量並理解你的決定所造成的影響。

改變圖片尺寸

除了能看到基本的繪製記錄,實際上我們還能得到額外的一些資訊,特別是和圖片相關的。如果你在記錄過程中上下調整頁面大小,就會看到有些繪製記錄允許你開啟它來獲得更多細節,然後你就應該看到改變圖片尺寸的記錄。也就是說:瀏覽器將調整圖片尺寸作為渲染工作的一部分。

Chrome 開發者工具中的改變圖片尺寸記錄

如果你向一個裝置發送了大圖片,然後用 CSS 或圖片的寬高屬性使之縮小,那麼你就會看到類似上面的記錄。需要瀏覽器重新調整尺寸的圖片個數和觸發的頻率會影響頁面的效能,因為它們發生在主瀏覽器執行緒,會阻礙其他操作。

當然了,不改變圖片尺寸的例子也有很多,但是在某些情況下是無法避免縮放操作的,尤其是在移動端,使用者只要用雙指捏拉就可以實現縮放。這些時候你是沒有什麼辦法的,但知道它會發生總歸是好事。不過話又說回來,瀏覽器的效能始終在提升,尤其是渲染這個熱門話題,因此你可以寄希望於技術提升所帶來的效能最佳化。

除了上面提到的內容,以下這些也會影響到滾動效能:

  • 耗效能樣式(Expensive Styles)
  • 重排(reflow)與重繪(repaint)
  • 反跳滾動事件失敗

接下來深入探討下這些問題。

耗效能樣式

首先要說明的是,不同樣式在消耗效能方面是不同的,有些效果(如經常被人提起的 box-shadow)從渲染角度來講十分耗效能,原因就是與其他樣式相比,它們的繪製程式碼執行時間過長。這就是說,如果一個耗效能嚴重的樣式經常需要重繪,那麼你就會遇到效能問題。其次你要知道,沒有不變的事情,在今天效能很差的樣式,可能明天就被最佳化,並且瀏覽器之間也存在差異。因此關鍵在於,你要藉助開發工具來分辨出效能瓶頸所在,然後設法減少瀏覽器的工作量。

在 Google I/O 2012 上,Nat Duca 和 Tom Wiltzius 對如何在 Chrome 中避免渲染卡頓做了出色的演講,並且給出了許多實用技巧,包括耗費昂貴的樣式與如何計算它們造成的影響。

影片地址:https://www.youtube.com/watch?v=hAzhayTnhEI

重排與重繪

無論何時你在 JavaScript 中獲取元素的 offsetTop 屬性,就相當於立即為瀏覽器佈置了大量任務,它必須馬上佈局頁面來為你傳回正確答案。這個過程就稱為重排。當我們基於 offsetTop 的值來改變元素的其他屬性,那麼元素(還有它的複合層)就需要重繪。它還會帶來一個連鎖反應,即由於重繪而導致 offsetTop 的值失效,因為對瀏覽器來說,頁面發生了變化。

當你對多個元素做上述操作時就可能引發嚴重問題。如果我們為每個元素計算位置然後重繪它,就會使瀏覽器進入耗能高且浪費的重排-重繪週期中。在這種情況下,我們應該透過兩個步驟來減少週期:首先,收集元素的 offsetTop 值,然後,完成視覺更新。透過這樣的方式,我們就避免了重覆計算元素的位置,假設我們使用requestAnimationFrame,那麼就可以讓視覺更新按照瀏覽器的最優時間來安排計劃。

值得一提的是,除了 offsetTop 之外的其他操作也會引起重排,最好瞭解一下這些內容來提高警惕。

反跳滾動事件(Debouncing Scroll Events)

假設你正開發一個視差滾動網站。你很自然的想到在獲得滾動事件時對視覺進行更新。主要的問題在於滾動事件和瀏覽器的視覺更新並不合拍,比如說在requestAnimationFrame 的回呼函式中,就會出現在一個渲染幀內多次更新的風險。如果更新操作耗費很高,視差滾動網站通常都會如此(大量被破壞的區域,很多重繪與組合操作),那麼這些重覆操作就很浪費。要解決這一問題,你需要反跳滾動事件。當滾動事件觸發時你就把上一次滾動的值保持在變數中,然後在requestAnimationFrame 中執行視覺更新,使用最後已知的那個值。如此一來,瀏覽器可以在正確的時間裡來安排視覺更新,而我們在每一幀中都只做必要的工作。

我們在之前一篇文章中介紹過重排/重繪週期,如果想瞭解更多內容不放看看這篇文章。

總結

現在你應該明白如何透過 Chrome 開發者工具的時間線來評估你的應用在滾動中的效能表現。你還要時常重覆這樣的工作,因為效能特性無時無刻不在變化,有些特性在今天比別的特性慢,但過不了多久可能就會反過來,它們隨著時間的變化而不同,關鍵在於如何解讀你看到的資料,並以此來調整最佳化的手段。

你應當始終檢查在開發者工具中的真實效能資料。用眼睛觀察是很簡單,但問題不一定那麼明顯。所以別靠猜,靠測試。

更多內容

如果你對瀏覽器內部實現或避免渲染效能瓶頸感興趣,請閱讀下麵這些內容。

  1. Tali Garsiel & Paul Irish on how browsers work
  2. A summary of hardware compositing in Chrome
  3. Paul Lewis on debouncing scroll events and reflow / repaint cycles
  4. Tom Wiltzius on jank busting for better rendering performance


來自:Html5Rocks (@牛肉火箭 翻譯)

連結:http://www.html5rocks.com/zh/tutorials/speed/scrolling/


贊(0)

分享創造快樂