布局是浏览器计算元素几何信息的地方 - 它们在页面中的大小和位置。每个元素都将具有基于所使用的 CSS、元素内容或父元素的显式或隐式尺寸信息。此过程在 Chrome 中称为“布局”。
布局是浏览器计算元素几何信息的地方:它们在页面中的大小和位置。每个元素都将具有基于所使用的 CSS、元素内容或父元素的显式或隐式尺寸信息。此过程在 Chrome(以及 Edge 等衍生浏览器)和 Safari 中称为“布局”。在 Firefox 中,它被称为“回流”,但该过程实际上是相同的。
与样式计算类似,布局成本的直接关注点是
- 需要布局的元素数量,这是页面 DOM 大小的副产品。
- 这些布局的复杂性。
摘要
- 布局对交互延迟有直接影响
- 布局通常限定于整个文档。
- DOM 元素的数量会影响性能;您应尽可能避免触发布局。
- 避免强制同步布局和布局抖动;先读取样式值,然后再进行样式更改。
布局对交互延迟的影响
当用户与页面交互时,这些交互应尽可能快。交互完成所需的时间(在浏览器呈现下一帧以显示交互结果时结束)称为交互延迟。这是 交互到下次绘制 指标衡量的页面性能的一个方面。
浏览器呈现下一帧以响应用户交互所需的时间称为交互的呈现延迟。交互的目标是提供视觉反馈,以便向用户发出已发生某些事情的信号,而视觉更新可能涉及一些布局工作才能实现该目标。
为了尽可能降低网站的 INP,尽可能避免布局非常重要。如果无法完全避免布局,则限制布局工作非常重要,以便浏览器可以快速呈现下一帧。
尽可能避免布局
当您更改样式时,浏览器会检查是否有任何更改需要计算布局,以及是否需要更新该渲染树。对“几何属性”(如宽度、高度、左侧或顶部)的更改都需要布局。
.box {
width: 20px;
height: 20px;
}
/**
* Changing width and height
* triggers layout.
*/
.box--expanded {
width: 200px;
height: 350px;
}
布局几乎总是限定于整个文档。如果您有很多元素,则需要很长时间才能计算出所有元素的位置和尺寸。
如果无法避免布局,那么关键是再次使用 Chrome DevTools 来查看布局花费的时间,并确定布局是否是瓶颈的原因。首先,打开 DevTools,转到“时间轴”标签页,点击“记录”并与您的网站互动。当您停止记录时,您将看到网站性能的细分

在深入研究上面示例中的跟踪记录时,我们看到每帧在布局中花费了超过 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);
}
如果您在询问“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');
}
在大多数情况下,您不应需要应用样式,然后查询值;使用上一帧的值应该足够了。同步且早于浏览器希望的时间运行样式计算和布局是潜在的瓶颈,通常不是您想要做的事情。
避免布局抖动
有一种方法可以使强制同步布局更糟:快速连续地执行大量布局。看看这段代码
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,它可以自动批量处理您的读取和写入操作,并应防止您意外触发强制同步布局或布局抖动。