版面配置是指瀏覽器判斷元素幾何資訊 (元素在頁面中的大小和位置) 的過程。每個元素都會根據所使用的 CSS、元素內容或父元素,提供明確或隱含的大小資訊。這項程序稱為 Chrome 中的版面配置。
版面配置是瀏覽器用來判斷元素幾何資訊的過程,包括元素的大小和網頁中的位置。每個元素都會根據所使用的 CSS、元素內容或父元素,提供明確或隱含的大小資訊。在 Chrome (以及 Edge 等衍生瀏覽器) 和 Safari 中,這個程序稱為「版面配置」。在 Firefox 中則名為 Reflow,但程序大致相同。
與樣式計算方式類似,版面配置成本的立即性考量因素如下:
- 需要版面配置的元素數量,這是網頁 DOM 大小的副產品。
- 這些版面配置的複雜度。
摘要
- 版面配置會直接影響互動延遲時間
- 版面配置通常會套用至整份文件。
- 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 通常會導致較高的版面配置成本。
避免強制同步版面配置
如何在螢幕上提交影格:
首先,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,這項功能會自動為您批次處理讀取和寫入作業,並避免您意外觸發強制同步版面配置或版面配置耗損。