web 應用開發最佳實踐之一:避免大型、複雜的佈局和佈局抖動

Avoid Large, Complex Layouts and Layout Thrashing

佈局是瀏覽器計算元素幾何信息的地方:即元素在頁面中的大小和位置。 每個元素都將具有基於所使用的 CSS、元素的內容或父元素的顯式或隱式大小信息。 該過程在 Chrome、Opera、Safari 和 Internet Explorer 中稱爲佈局(Layout).

在 Firefox 中,它被稱爲迴流(reflow),但實際上過程是相同的。

與樣式計算類似,佈局成本的直接關注點是:

  • 需要佈局的元素數量。
  • 這些佈局的複雜性。

簡而言之:

  • 佈局通常限定於整個文檔。
  • DOM 元素的數量會影響性能; 你應該儘可能避免觸發佈局。
  • 評估佈局模型性能; 新的 Flexbox 通常比舊的 Flexbox 或基於浮動的佈局模型更快。
  • 避免強制同步佈局和佈局抖動; 讀取樣式值然後進行樣式更改。

Avoid layout wherever possible

當您更改樣式時,瀏覽器會檢查是否有任何更改需要計算佈局,以及是否需要更新渲染樹。 更改“幾何屬性”,例如寬度、高度、左側或頂部都需要執行佈局過程。

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

/**
 * Changing width and height
 * triggers layout.
 */
.box--expanded {
  width: 200px;
  height: 350px;
}

佈局幾乎總是作用於整個文檔。 如果您有很多元素,則需要很長時間才能弄清楚它們的位置和尺寸。

如果無法避免佈局,那麼關鍵是再次使用 Chrome DevTools 來查看需要多長時間,並確定佈局是否是造成瓶頸的原因。 首先,打開 DevTools,轉到 Timeline 選項卡,點擊記錄並與您的站點進行交互。 當您停止錄製時,您會看到您的網站表現的細分:

在上例中深入研究幀時,我們看到在佈局內部花費了超過 20 毫秒,當我們有 16 毫秒在動畫中在屏幕上顯示幀時,這太高了。 您還可以看到 DevTools 會告訴您樹的大小(在本例中爲 1,618 個元素),以及需要佈局的節點數量。

Avoid forced synchronous layouts

將網頁運送到屏幕具有以下順序:

首先運行 JavaScript,然後是樣式計算,然後是佈局。 但是,可以使用 JavaScript 強制瀏覽器提前執行佈局。 它被稱爲強制同步佈局。

首先要記住的是,當 JavaScript 運行時,前一幀中的所有舊佈局值都是已知的,可供您查詢。 因此,例如,如果您想在幀的開頭寫出元素的高度(讓我們稱其爲“框”),您可以編寫如下代碼:

// 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);
}

現在,爲了回答高度問題,瀏覽器必須先應用樣式更改(因爲添加了超大類),然後運行佈局。 只有這樣,它才能返回正確的高度。 這是不必要的並且可能是昂貴的工作。

因此,您應該始終批量讀取樣式並首先執行(瀏覽器可以使用前一幀的佈局值),然後執行任何寫入:

正確完成上述功能將是:

function logBoxHeight() {
  // Gets the height of the box in pixels
  // and logs it out.
  console.log(box.offsetHeight);

  box.classList.add('super-big');
}

在大多數情況下,您不需要應用樣式然後查詢值; 使用最後一幀的值就足夠了。 同步運行樣式計算和佈局並早於瀏覽器的預期是潛在的瓶頸,而不是您通常想要做的事情。

Avoid layout thrashing

有一種方法可以使強制同步佈局變得更糟:快速連續地進行大量佈局。 看看這段代碼:

function resizeAllParagraphsToMatchBlockWidth() {

  // Puts the browser into a read-write-read-write cycle.
  for (var i = 0; i < paragraphs.length; i++) {
    paragraphs[i].style.width = box.offsetWidth + 'px';
  }
}

此代碼遍歷一組段落並設置每個段落的寬度以匹配名爲“box”的元素的寬度。 它看起來無害,但問題是循環的每次迭代都會讀取一個樣式值(box.offsetWidth),然後立即使用它來更新段落的寬度(paragraphs[i].style.width)。 在循環的下一次迭代中,瀏覽器必須考慮自上次請求 offsetWidth(在前一次迭代中)以來樣式已更改的事實,因此它必須應用樣式更改並運行佈局。 這將在每次迭代中發生!。

此示例的修復方法是再次讀取然後寫入值:

// Read.
var width = box.offsetWidth;

function resizeAllParagraphsToMatchBlockWidth() {
  for (var i = 0; i < paragraphs.length; i++) {
    // Now write.
    paragraphs[i].style.width = width + 'px';
  }
}

更多Jerry的原創文章,盡在:“汪子熙”:

本文同步分享在 博客“汪子熙”(CSDN)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章