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

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

版面配置是瀏覽器用來判斷元素幾何資訊的過程,包括元素的大小和網頁中的位置。每個元素都會根據所使用的 CSS、元素內容或父元素,提供明確或隱含的大小資訊。在 Chrome (以及 Edge 等衍生瀏覽器) 和 Safari 中,這個程序稱為「版面配置」。在 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 毫秒,因此這個值過高。您也可以看到,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`;
 
}
}

如要確保安全性,建議您使用 FastDOM,這項功能會自動為您批次處理讀取和寫入作業,並避免您意外觸發強制同步版面配置或版面配置耗損。