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

版面配置是指瀏覽器算出元素的幾何資訊,例如元素的大小和頁面中的位置。每個元素都會根據使用的 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 提供。