避免不必要的繪製

Paul Lewis

簡介

為網站或應用程式繪製元素會耗費大量成本,而且可能會對執行階段效能造成負面影響。本文將簡要說明瀏覽器可觸發繪圖的原因,以及如何避免發生不必要的繪製。

繪畫:超快速導覽

瀏覽器必須執行的其中一項主要工作,是將 DOM 和 CSS 轉換成螢幕上的像素,而且還要透過相當複雜的程序完成。系統會先讀取標記,然後從中建立一個 DOM 樹狀結構。這麼做也與 CSS 類似,進而建立 CSSOM。然後,DOM 和 CSSOM 會結合在一起,最後得出一個可以開始繪製像素的結構。

創作過程本身很有趣。在 Chrome 中,可透過部分名為 Skia 的軟體光柵化 DOM 和 CSS 的樹狀結構。如果您曾玩過 canvas 元素 Skia 的 API,就不應該熟悉了;有許多 moveTolineTo 樣式的功能,以及許多更進階的函式。基本上,所有需要繪製的元素都會擷取至可執行的 Skia 呼叫集合,而輸出結果是一堆點陣圖。這些點陣圖會上傳到 GPU,GPU 進行合成,藉此呈現出在螢幕上的最終成果。

點至像素

首先要注意的是,Skia 工作負載會直接受到套用於元素的樣式影響。如果你使用大量的演算法樣式,那麼 Skia 還需要完成更多工作。Colt McAnlis 撰寫了一篇文章,說明 CSS 對網頁轉譯權重的影響,因此建議您參閱相關說明。

有鑑於此,繪畫工作需要時間才能運作。如果不減少繪製,畫面預算就會超過影格預算約 16 毫秒。使用者會注意到我們缺少影格,並認為內容卡頓,進而對應用程式的使用者體驗帶來負面影響。我們真正不希望這種使用行為,因此先來看看哪種問題會導致繪圖作業的必要性,以及我們該怎麼做。

捲動

每當您在瀏覽器中向上或向下捲動網頁時,都需要重新繪製內容,其內容才會顯示。一切看起來都夠小,但就算是必須繪製的元素,也能套用複雜的樣式。所以就算只著一點的小區域塗上,也不代表一切很快就會發生。

如要瞭解哪些區域正在重繪,請使用 Chrome 開發人員工具中的「顯示繪製矩形」功能 (點選右下角的小齒輪)。接著,開啟開發人員工具後,只要與頁面互動,您就會看到 Chrome 繪製網頁某個部分的位置和時間點。

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

捲動效能是網站成功與否的關鍵,使用者確實會注意到網站或應用程式捲動位置不佳,導致不喜歡。因此,我們希望在捲動畫面時照亮顏料的工作,以免使用者發現卡頓。

我們已寫一篇有關捲動效能的文章,如要進一步瞭解捲動效能的具體細節,請參閱這篇文章。

互動

互動也是繪製問題的另一個原因:懸停、點擊、輕觸、拖曳。當使用者執行上述其中一項互動時,比如懸停,Chrome 就必須重新繪製受影響的元素。就像捲動畫面一樣,如果需要大量複雜的顏料,也都會發生影格速率下降的情況。

大家都想獲得美觀、順暢的互動動畫,因此我們也必須瞭解變更動畫樣式是否會浪費太多時間。

驚奇的組合

展示昂貴畫作的示範影片
展示昂貴色彩的示範內容

如果我捲動畫面然後同時移動滑鼠,會發生什麼事?我很有可能在我捲動畫面時「無意間」與某個元素「互動」,因而產生昂貴的顏料。因此,我們可以將影格預算維持在約 16.7 毫秒 (也就是我們必須維持在這個資料量,才能達到每秒 60 個影格數)。我已製作示範模式,以便瞭解確切的意思。希望您能夠在捲動及移動滑鼠時,看到懸停效果的效果好,不過我們來看看 Chrome 開發人員工具的功用:

Chrome 開發人員工具顯示大量影格
Chrome 的開發人員工具顯示較昂貴影格

上圖顯示,將遊標懸停在其中一個區塊上時,開發人員工具正在註冊繪製作業。為了達到這個重點,我在示範中融入了非常繁重的風格,因此偶爾會大幅增加影格預算。最後,我最不想做的就是有不必要的繪製工作,尤其是捲動期間有其他工作需要完成!

怎麼避免這種情況發生?發生這類問題時,導入方法相當簡單。秘訣是附加 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');
}

如您所見,我們會使用主體上的類別追蹤是否「允許」懸停效果,而基礎樣式則需要使用這個類別:

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

就是這麼簡單!

結論

對應用程式感到滿意時,轉譯效能非常重要,您應將繪製工作負載控制在 16 毫秒以內。為協助您達到這個目的,請在整個開發過程中使用開發人員工具整合,找出出現瓶頸並立即修正。

無意間互動可能會耗費大量成本,導致轉譯效能降低。如您所見,我們可以使用一小段程式碼來修正問題。

以網站和應用程式為例,它們是否可以設定一些顏料保護?