避免不必要的繪製

Paul Lewis

簡介

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

繪圖:快速導覽

瀏覽器必須執行的主要工作之一,就是將 DOM 和 CSS 轉換為螢幕上的像素,而這項作業會透過相當複雜的程序完成。首先會讀取標記,然後建立 DOM 樹狀結構。這個程式碼與 CSS 相同,由其建立 CSSOM。接著,我們會將 DOM 和 CSSOM 合併,最終取得可用來著色一些像素的結構。

繪圖過程本身就很有趣。在 Chrome 中,DOM 和 CSS 的組合樹狀結構會由名為 Skia 的軟體進行轉譯。如果您曾使用過 canvas 元素 Skia 的 API,看起來對我們來說並不陌生,除了 moveTolineTo 樣式的 API 外,還有許多進階項目。基本上,所有需要繪製的元素都會針對一組可執行的 Skia 呼叫進行蒸餾,而 的輸出結果是許多點陣圖。這些點陣圖會上傳至 GPU,GPU 可合併圖像,得到畫面上的最終圖像。

點對像素

重點在於 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 進行整合,以便在瓶頸出現時加以識別並修正。

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

看看您的網站和應用程式,能否搭配些許色彩保護?