避免發生複雜的大型版面配置和版面配置閃爍

版面配置是指瀏覽器算出元素的幾何資訊,例如元素的大小和頁面中的位置。每個元素都會根據使用的 CSS、元素內容或父項元素,提供明確或隱含的大小資訊。這項程序稱為 Chrome 中的版面配置。

版面配置是指瀏覽器算出元素的幾何圖形資訊位置,包括元素的大小和頁面中的位置。每個元素都會根據使用的 CSS、元素內容或父項元素,提供明確或隱含的大小資訊。這項程序稱為 Chrome 中的 Layout (以及 Edge 等衍生瀏覽器),在 Firefox 中則名為 Reflow,但程序大致相同。

與樣式計算類似,版面配置成本立即會有以下疑慮:

  1. 需要版面配置的元素數量,也就是頁面 DOM 大小的副作用。
  2. 這些版面配置的複雜度。

摘要

  • 版面配置對互動延遲時間有直接影響
  • 版面配置通常會限定在整份文件內。
  • DOM 元素數量會影響效能;都應避免觸發版面配置
  • 避免強制同步版面配置和版面配置輾轉現象;讀取樣式值,然後變更樣式。

版面配置對互動延遲的影響

當使用者與網頁互動時,這些互動必須越快越好。廣告互動完成所需的時間長度 (直到瀏覽器顯示下一個影格,以顯示互動結果為止),稱為「互動延遲」。這是網頁效能的重點,也就是「與下一個顯示內容的互動」指標衡量的結果。

瀏覽器為了回應使用者互動而顯示下一個影格所需的時間,稱為互動的「顯示延遲」。互動的目的是提供視覺回饋,告知使用者發生了某個情況,而視覺更新可能涉及一定程度的版面配置來達成該目標。

為了確保網站的 INP 越低,請盡量避免使用版面配置。如果您無法完全避免版面配置,請務必限製版面配置的使用,讓瀏覽器可以快速呈現下一個影格。

盡量避免版面配置

當您變更樣式時,系統會檢查任何變更是否需要計算版面配置,並更新轉譯樹狀結構。變更「幾何屬性」(例如寬度、高度、左側或頂端) 都需要版面配置。

.box {
  width: 20px;
  height: 20px;
}

/**
  * Changing width and height
  * triggers layout.
  */

.box--expanded {
  width: 200px;
  height: 350px;
}

版面配置幾乎一律會限定在整份文件的範圍。如果您有許多元素,就要花很久的時間找出這些元素的位置和尺寸。

如果無法避免版面配置,請再次使用 Chrome 開發人員工具查看操作時間,判斷版面配置是否為瓶頸的原因。首先,開啟開發人員工具並前往「時間軸」分頁,按一下「錄製」,然後與網站互動。停止記錄後,您將會看到網站成效的細分資料:

開發人員工具顯示在版面配置中很長的時間。

深入瞭解上述範例的追蹤記錄時,我們發現每個影格在版面配置中花費的時間超過 28 毫秒,而如果我們需要 16 毫秒才能在動畫中顯示影格,會耗費超過 28 毫秒。您也可以看到開發人員工具會指出樹狀結構大小 (在本例中為 1,618 個元素),以及需要版面配置的節點數量 (在本例中為 5 個)。

請注意,我們在此提供的通用建議是「盡可能避免」版面配置,但並非每次都能避免版面配置。如果無法避免版面配置,請注意版面配置費用與 DOM 大小有關係。雖然兩者的關係並未緊密結合,但較大的 DOM 通常會產生較高的版面配置成本。

避免強制同步版面配置

如何在螢幕上提交影格:

使用 Flexbox 做為版面配置。

首先,JavaScript 會執行「然後」樣式計算,再執行版面配置。不過,您也可以強制瀏覽器使用 JavaScript 提早執行版面配置。這就是所謂的強制同步版面配置

首先須留意,由於 JavaScript 會執行前一個影格中的所有舊版面配置值,以供您查詢。舉例來說,如果您想要在影格開頭寫出元素高度 (我們稱「box」),您可以編寫類似下方的程式碼:

// Schedule our function to run at the start of the frame:
requestAnimationFrame(logBoxHeight);

function logBoxHeight () {
  // Gets the height of the box in pixels and logs it out:
  console.log(box.offsetHeight);
}

如果您在詢問方塊高度「之前」變更了方塊樣式,會造成問題:

function logBoxHeight () {
  box.classList.add('super-big');

  // Gets the height of the box in pixels and logs it out:
  console.log(box.offsetHeight);
}

現在,為了回答高度問題,瀏覽器必須先「先」套用樣式變更 (因為加入 super-big 類別),然後「接著」執行版面配置。然後它才能傳回正確的高度。這非必要的工作,而且可能需要耗費大量資源。

因此,您一律應該先批次讀取樣式,再執行這些動作 (瀏覽器可使用前一個影格的版面配置值),再執行任何寫入作業:

如果正確完成上述函式,就會如下所示:

function logBoxHeight () {
  // Gets the height of the box in pixels and logs it out:
  console.log(box.offsetHeight);

  box.classList.add('super-big');
}

在大部分的情況下,您都不需要套用樣式,然後查詢值。最後一個影格的值應該就足夠了以非同步或更早的方式執行樣式計算和版面配置,可能會遇到瓶頸,但您通常不會想要這麼做。

避免版面配置輾轉現象

有方法讓強制同步版面配置更臻完善,那就是快速連續執行多種版面配置。請參閱下列程式碼:

function resizeAllParagraphsToMatchBlockWidth () {
  // Puts the browser into a read-write-read-write cycle.
  for (let i = 0; i < paragraphs.length; i++) {
    paragraphs[i].style.width = `${box.offsetWidth}px`;
  }
}

這段程式碼是讓一組段落迴圈,並且設定每個段落的寬度,以符合名為「box」的元素寬度。看起來並沒有什麼問題,但問題是,迴圈的每個疊代都會讀取樣式值 (box.offsetWidth),然後立即用該值更新段落寬度 (paragraphs[i].style.width)。在下次迴圈疊代時,瀏覽器必須考量樣式自上次要求 offsetWidth 以來變更過 (在先前的疊代作業中),因此必須套用樣式變更,並執行版面配置。這種情況會在每次疊代時發生。

這個範例的修正方式是再次「讀取」,然後「寫入」值:

// Read.
const width = box.offsetWidth;

function resizeAllParagraphsToMatchBlockWidth () {
  for (let i = 0; i < paragraphs.length; i++) {
    // Now write.
    paragraphs[i].style.width = `${width}px`;
  }
}

如要確保安全,建議您使用 FastDOM,讓系統自動分批讀取和寫入資料,避免意外觸發強制同步的版面配置或版面配置輾轉現象。

主頁橫幅由 Hal Gatewood 提供。