版面配置是指瀏覽器算出元素的幾何資訊,例如元素的大小和頁面中的位置。每個元素都會根據使用的 CSS、元素內容或父項元素,提供明確或隱含的大小資訊。這項程序稱為 Chrome 中的版面配置。
版面配置是指瀏覽器算出元素的幾何圖形資訊位置,也就是元素的大小和頁面中的位置。每個元素都會根據使用的 CSS、元素內容或父項元素,提供明確或隱含的大小資訊。這項程序稱為 Chrome 中的 Layout (以及 Edge 等衍生瀏覽器),在 Firefox 中則名為 Reflow,但程序大致相同。
與樣式計算類似,版面配置成本立即會有以下疑慮:
- 需要版面配置的元素數量,也就是頁面 DOM 大小的副作用。
- 這些版面配置的複雜度。
摘要
- 版面配置會直接影響互動延遲
- 版面配置通常會限定在整份文件內。
- DOM 元素數量會影響效能,因此請盡量避免觸發版面配置。
- 避免強制同步版面配置和版面配置輾轉現象;先讀取樣式值再變更樣式。
版面配置對互動延遲的影響
當使用者與網頁互動時,這些互動必須越快越好。廣告互動完成所需的時間長度 (直到瀏覽器顯示下一個影格,以顯示互動結果為止),稱為「互動延遲」。這是網頁效能的重點,也就是「與下一個顯示內容的互動」指標衡量的結果。
瀏覽器為了回應使用者互動而顯示下一個影格所需的時間,稱為互動的「顯示延遲」。互動的目的是提供視覺回饋,告知使用者發生了某個情況,而視覺更新可能涉及一定程度的版面配置來達成該目標。
為了確保網站的 INP 越低,請盡量避免使用版面配置。如果您無法完全避免版面配置,請務必限製版面配置的使用,讓瀏覽器可以快速呈現下一個影格。
盡量避免版面配置
當您變更樣式時,系統會檢查任何變更是否需要計算版面配置,並更新轉譯樹狀結構。變更「幾何屬性」(例如寬度、高度、左側或頂端) 都需要版面配置。
.box {
width: 20px;
height: 20px;
}
/**
* Changing width and height
* triggers layout.
*/
.box--expanded {
width: 200px;
height: 350px;
}
版面配置幾乎一律會限定在整份文件的範圍。如果您有許多元素,就要花很久的時間找出這些元素的位置和尺寸。
如果無法避免版面配置,請再次使用 Chrome 開發人員工具查看操作時間,判斷版面配置是否為瓶頸的原因。首先,開啟開發人員工具並前往「時間軸」分頁,按一下「錄製」,然後與網站互動。停止記錄後,您將會看到網站成效的細分資料:
![開發人員工具顯示在版面配置中很長的時間。](https://web.dev/static/articles/avoid-large-complex-layouts-and-layout-thrashing/image/devtools-showing-long-i-9f35099ab9d22.png?authuser=7&hl=zh-tw)
深入瞭解上述範例的追蹤記錄時,我們發現每個影格在版面配置中花費的時間超過 28 毫秒,而如果我們需要 16 毫秒才能在動畫中顯示影格,會耗費超過 28 毫秒。您也可以看到開發人員工具會指出樹狀結構大小 (在本例中為 1,618 個元素),以及需要版面配置的節點數量 (在本例中為 5 個)。
請注意,我們在此提供的通用建議是「盡可能避免」版面配置,但並非每次都能避免版面配置。如果無法避免版面配置,請注意版面配置費用與 DOM 大小有關係。雖然兩者的關係並未緊密結合,但較大的 DOM 通常會產生較高的版面配置成本。
避免強制同步版面配置
如何在螢幕上提交影格:
![使用 Flexbox 做為版面配置。](https://web.dev/static/articles/avoid-large-complex-layouts-and-layout-thrashing/image/using-flexbox-layout-0c9955c54a296.jpg?authuser=7&hl=zh-tw)
首先,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 提供。