避免不必要的繪製

Paul Lewis

簡介

為網站或應用程式繪製元素可能會耗費大量資源,並對執行階段效能產生負面連鎖效應。本文將快速介紹哪些因素會在瀏覽器中觸發繪圖作業,以及如何避免不必要的繪圖作業。

繪圖:快速導覽

瀏覽器必須執行的主要工作之一,就是將 DOM 和 CSS 轉換為螢幕上的像素,而這項作業會透過相當複雜的程序完成。首先會讀取標記,然後建立 DOM 樹狀結構。它會對 CSS 執行類似的操作,並從中建立 CSSOM。接著,我們會將 DOM 和 CSSOM 合併,最終取得可用來繪製部分像素的結構。

繪圖過程本身就很有趣。在 Chrome 中,DOM 和 CSS 的組合樹狀結構會由名為 Skia 的軟體進行轉成點陣圖。如果您曾經玩過 canvas 元素,Skia 的 API 會讓您覺得非常熟悉,因為它包含許多 moveTolineTo 樣式的函式,以及許多更進階的函式。基本上,所有需要繪製的元素都會精煉為可執行的 Skia 呼叫集合,而輸出內容則是一堆位圖。這些點陣圖會上傳至 GPU,而 GPU 會將這些圖片合成,讓我們在螢幕上看到最終圖片。

Dom 到像素

請注意,Skia 的工作量會直接受到您套用至元素的樣式影響。如果您使用演算法繁重的樣式,Skia 就必須做更多工作。Colt McAnlis 曾撰寫一篇文章,說明 CSS 如何影響網頁轉譯重量,建議您參閱該文章,進一步瞭解相關資訊。

話雖如此,繪製作業需要時間才能完成,如果不縮短時間,就會超過約 16 毫秒的影格預算。使用者會發現我們遺漏了影格,並認為這是卡頓現象,這最終會影響應用程式的使用者體驗。我們絕對不希望發生這種情況,因此讓我們看看哪些因素會導致需要進行繪圖作業,以及我們可以採取哪些行動。

捲動

無論您在瀏覽器中向上或向下捲動,都需要重新繪製內容,才能顯示在畫面上。一切順利的話,這只是一個小範圍,但即使如此,需要繪製的元素可能會套用複雜的樣式。因此,即使你只需要塗抹小範圍的牆面,也不代表塗裝作業會很快完成。

如要查看哪些區域正在重繪,您可以使用 Chrome 開發人員工具中的「顯示繪圖矩形」功能 (只要按下右下角的小齒輪)。接著,開啟開發人員工具,只要與網頁互動,您就會看到閃爍的矩形,顯示 Chrome 繪製網頁部分內容的位置和時間。

在 Chrome 開發人員工具中顯示繪製矩形區
在 Chrome 開發人員工具中顯示繪圖矩形

捲動效能對網站的成功至關重要。如果網站或應用程式的捲動功能不佳,使用者會察覺並感到不滿。因此,我們有必要在捲動期間保持輕量繪圖作業,以免使用者看到卡頓情形。

我先前曾撰寫一篇關於捲動效能的文章,如果您想進一步瞭解捲動效能的具體資訊,請參閱該文章。

互動

互動行為也是導致繪製作業的原因之一,包括懸停、點擊、觸控和拖曳。每當使用者執行其中一種互動 (例如游標),Chrome 就必須重新繪製受影響的元素。與捲動畫面類似,如果需要大量複雜的繪圖,您會發現影格速率會下降。

每個人都希望能看到流暢的互動動畫,因此我們需要再次確認動畫中變更的樣式是否耗費太多時間。

不幸的組合

使用昂貴顏料的示範
使用昂貴顏料的示範畫面

如果我同時捲動畫面和移動滑鼠,會發生什麼情況?當我捲動畫面時,很可能會「無意」與某個元素「互動」,進而觸發高成本的繪圖作業。這反過來說,可能會讓我超出約 16.7 毫秒的影格預算 (我們需要保持在這個時間以下,才能達到每秒 60 影格)。我已製作示範,讓您瞭解我的意思。希望您在捲動和移動滑鼠時,會看到懸停效果生效,但讓我們看看 Chrome 開發人員工具如何處理:

Chrome 開發人員工具顯示耗用大量資源的畫面
Chrome 開發人員工具顯示耗用大量資源的框架

在上述圖片中,您可以看到當我將滑鼠游標懸停在其中一個區塊時,DevTools 會註冊繪製作業。為了說明重點,我在這項示範中使用了一些超重樣式,因此我會將影格預算提高,偶爾也會超出影格預算。我最不希望發生的情況,就是不必要地執行此繪製作業,尤其是在捲動期間,因為這時還有其他工作要執行!

那麼,我們該如何避免這種情況發生?實作修正方式相當簡單。這裡的訣竅是附加 scroll 處理常式,以便停用懸停效果,並設定計時器以便再次啟用效果。也就是說,我們保證在您捲動畫面時,不會執行任何耗費資源的互動繪製作業。當你停車一段時間後,我們會判斷是否可以安全地重新開啟。

程式碼如下:

// Used to track the enabling of hover effects
var enableTimer = 0;

/*
 * Listen for a scroll and use that to remove
 * the possibility of hover effects
 */
window.addEventListener('scroll', function() {
  clearTimeout(enableTimer);
  removeHoverClass();

  // enable after 1 second, choose your own value here!
  enableTimer = setTimeout(addHoverClass, 1000);
}, false);

/**
 * Removes the hover class from the body. Hover styles
 * are reliant on this class being present
 */
function removeHoverClass() {
  document.body.classList.remove('hover');
}

/**
 * Adds the hover class to the body. Hover styles
 * are reliant on this class being present
 */
function addHoverClass() {
  document.body.classList.add('hover');
}

如您所見,我們在 body 上使用了一個類別,用於追蹤是否允許懸停效果,而基礎樣式則需要此類別才能顯示:

/* Expect the hover class to be on the body
 before doing any hover effects */
.hover .block:hover {
 
}

就是這樣!

結論

轉譯效能對使用者享受應用程式至關重要,因此您應一律設法將繪圖工作負載控制在 16 毫秒以內。為協助您完成這項工作,請在整個開發過程中使用 DevTools 進行整合,以便在發生時找出並修正瓶頸。

無意義的交互作用 (尤其是在繪製密集的元素上) 可能會耗用大量資源,並降低轉譯效能。如您所見,我們可以使用一小段程式碼來修正這個問題。

請查看您的網站和應用程式,看看是否需要一些保護措施。