避免大型,复杂的布局和布局颠覆
布局是浏览器查找元素的几何信息:其大小和页面中的位置。每个元素将基于所使用的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,这将自动为您分配读取和写入数据,并且应防止您意外触发强制同步布局或布局颠覆。