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

發布日期:2015 年 3 月 20 日,上次更新日期:2025 年 5 月 7 日

版面配置是瀏覽器用來判斷元素幾何資訊的過程,包括元素的大小和網頁中的位置。每個元素都會根據所使用的 CSS、元素內容或父元素,提供明確或隱含的大小資訊。在 Chrome (以及 Edge 等衍生瀏覽器) 和 Safari 中,這個程序稱為「版面配置」。在 Firefox 中,這項功能稱為 Reflow,但實際上兩者處理程序相同。

與樣式計算方式類似,版面配置成本的立即考量重點如下:

  1. 需要版面配置的元素數量,這是網頁 DOM 大小的副產品。
  2. 版面配置的複雜度。

摘要

  • 版面配置會直接影響互動延遲時間
  • 版面配置通常會套用至整份文件。
  • DOM 元素的數量會影響效能,因此請盡可能避免觸發版面配置。
  • 避免強制同步版面配置和版面配置耗盡;請讀取樣式值,然後變更樣式。

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

使用者與網頁互動時,這些互動應盡可能快速。互動完成所需的時間 (結束時間為瀏覽器呈現下一個影格,以顯示互動結果) 稱為互動延遲。這是「與下一個顯示的內容互動」指標評估的網頁效能面向。

瀏覽器在回應使用者互動時,呈現下一個影格所需的時間,稱為互動的呈現延遲。互動的主要目的是提供視覺回饋,向使用者傳達發生的事件,而視覺更新可能需要進行一些版面配置工作才能達成這個目標。

為了盡可能降低網站的 INP,請盡量避免使用版面配置。如果無法完全避免版面配置,請務必限制版面配置作業,讓瀏覽器能快速顯示下一個影格。

盡量避免使用版面配置

變更樣式時,瀏覽器會檢查是否有任何變更需要計算版面配置,以及是否需要更新該轉譯樹狀結構。您必須使用版面配置,才能變更「幾何屬性」,例如 widthheightlefttop

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

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

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

版面配置的範圍幾乎總是整份文件。如果元素很多,您可能需要花費很長的時間來找出所有元素的位置和尺寸。

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

開發人員工具顯示多個紫色版面配置區塊。
Chrome 開發人員工具在「版面配置」中顯示的時間過長。

深入探討前述範例中的追蹤記錄,我們發現每個影格在版面配置中花費的時間超過 28 毫秒,而動畫在畫面上顯示一個影格只需要 16 毫秒,因此這個時間過長。您也可以看到,DevTools 會告訴您樹狀結構的大小 (在本例中為 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`;
  }
}

找出強制同步版面配置和耗損

DevTools 提供「強制重新流」洞察資料,協助您快速找出強制同步版面配置 (也稱為「強制重新流」) 的情況:

開發人員工具顯示強制重新流動的洞察資料,指出導致強制重新流動的函式為「w」。
Chrome 開發人員工具強制重新整理洞察資訊。

您也可以使用 forcedStyleAndLayoutDuration 屬性,透過 Long Animation Frame API 指令碼歸因,在欄位中識別強制同步版面配置。