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

版面配置是瀏覽器判別元素的幾何圖形資訊,也就是圖片大小及網頁中的位置。每個元素都會根據使用的 CSS、元素內容或父項元素,提供明確或隱含的大小資訊。這項程序在 Chrome 中稱為「版面配置」。

版面配置是瀏覽器判別元素的幾何圖形資訊:網頁上的大小和位置。每個元素都會根據使用的 CSS、元素內容或父項元素,提供明確或隱含的大小資訊。這項程序稱為 Chrome 中的版面配置 (以及 Edge 等衍生瀏覽器) 和 Safari。在 Firefox 中稱為「重排」,但這項程序實際上是相同的。

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

  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 毫秒,畫面顯示的畫面就會太高。您也可以看到開發人員工具會指出樹狀結構大小 (在本例中為 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 撰寫,內容取自「Unsplash」。