避免大型,复杂的布局和布局颠覆

避免大型,复杂的布局和布局颠覆

布局是浏览器查找元素的几何信息:其大小和页面中的位置。每个元素将基于所使用的CSS,元素的内容或父元素显示或隐式的大小调整信息。该过程在 Chrome,Opera,Safari 和 Internet Explorer中称为“布局”。在 Firefox 中,它被称为 Reflow (回流),但是这个过程的作用是相同的。

类似于 style 计算,布局成本的直接相关是:

  • 需要布局的元素数量。
  • 这些布局的复杂性。

TL; DR

  • 布局通常作用于整个文档。
  • DOM 元素的数量将影响性能; 您应尽可能避免触发布局。
  • 评估布局模型性能; 新的Flexbox通常比旧版Flexbox或基于浮动的布局模型更快。
  • 避免强制同步布局和布局颠覆; 先读取样式值,然后再进行样式更改。

尽可能避免布局

当您更改样式时,浏览器将检查以查看是否有任何更改需要计算布局,并为该渲染树进行更新。对“几何属性”的更改,如宽度,高度,左侧或顶部都需要布局。

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

/**
 * 改变宽高
 * 触发 layout.
 */
.box--expanded {
  width: 200px;
  height: 350px;
}

布局几乎总是作用于整个文档。如果你有很多元素,那么需要很长时间来确定它们的位置和尺寸。

如果不可能避免布局,那么关键是再次使用 Chrome DevTools 来查看花费多长时间,并确定布局是否是瓶颈的原因。首先,打开 DevTools,转到“时间轴”选项卡,点击记录并与您的网站进行交互。当您停止录制时,您会看到您的网站执行的细目:

当在上面的例子中挖掘每一帧时,我们看到在布局中花费了超过20毫秒,当我们的屏幕在动画中有16ms时,这太高了。您还可以看到,DevTools 会告诉您 DOM 树的大小(在这种情况下为1,618个元素),以及需要多少个节点进行布局。

注意:想要明确列出哪些 CSS 属性触发布局(layout),绘画(paint)或复合(composite)?查看CSS触发器

在旧的布局模型上使用 flexbox

网络上有一系列的布局模式,其中一些比其他模型更受广泛支持。最旧的 CSS 布局模型允许我们在屏幕上进行相对,绝对地定位元素和浮动元素。

下面的截图显示了在1,300个盒子上使用浮点数时的布局成本。诚然,这是一个例证,因为大多数应用程序将使用各种方法来定位元素。

如果我们更新样本以使用 Flexbox,这是Web平台上最近的一个补充,我们得到了一个不同的图片:

现在,在相同数量的元素和相同的视觉外观的布局中,我们花费更少的时间(在这种情况下,3.5ms对14ms)。重要的是要记住,对于某些浏览器环境,您可能无法选择 Flexbox,因为它比浮动布局更不受支持,但您应该至少应该调查布局模型对您的性能的影响,并将其最小化执行成本

无论您是否选择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);
}

如果您在要求其高度之前更改了 box 的样式,那么事情会变得有问题:

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

在大多数情况下,您不应该需要应用样式,然后查询值; 使用最后一帧的值应该是足够的。过早地运行 style 计算和布局是浏览器同步和布局潜在的瓶颈,这不是您通常想要做的事情。

避免布局颠覆

有一种方法可以使强制同步布局更加糟糕:快速连续地做很多事情。看看这段代码:

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),然后立即使用它来更新一个paragraph(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';
  }
}

如果您想保证安全性,您应该检查FastDOM,这将自动为您分配读取和写入数据,并且应防止您意外触发强制同步布局或布局颠覆。

【翻译原文】:Avoid Large, Complex Layouts and Layout Thrashing

0%