浏览器重排(Reflow)与重绘(Repaint)的边界:利用transform与opacity实现零布局开销
大家好,今天我们来深入探讨浏览器渲染引擎中的两个关键概念:重排(Reflow)和重绘(Repaint),以及如何利用 transform 和 opacity 属性来优化性能,实现“零布局开销”。
渲染引擎的工作流程
在深入探讨重排和重绘之前,我们需要了解浏览器渲染引擎的基本工作流程。当浏览器接收到 HTML、CSS 和 JavaScript 代码后,会经历以下几个关键步骤:
-
解析 HTML 构建 DOM 树(DOM Tree): 浏览器解析 HTML 代码,构建一个树状结构,代表网页的结构。每个 HTML 元素对应 DOM 树中的一个节点。
-
解析 CSS 构建 CSSOM 树(CSS Object Model): 浏览器解析 CSS 代码,构建 CSSOM 树。CSSOM 包含了所有 CSS 规则,包括外部样式表、内联样式和浏览器默认样式。
-
渲染树(Render Tree)构建: 浏览器将 DOM 树和 CSSOM 树结合起来,构建渲染树。渲染树只包含需要显示的节点,以及它们的样式信息。例如,
display: none的元素不会出现在渲染树中。 -
布局(Layout,也称 Reflow 或 Layout): 渲染引擎遍历渲染树,计算每个节点在页面上的确切位置和大小。这个过程被称为布局或重排。
-
绘制(Paint,也称 Repaint): 渲染引擎遍历渲染树,将每个节点绘制到屏幕上。这个过程被称为绘制或重绘。
-
合成(Composite): 渲染引擎将页面分成多个图层,并将这些图层合并成最终的图像,显示在屏幕上。
重排(Reflow)与重绘(Repaint):性能瓶颈
重排和重绘是浏览器渲染过程中最耗费性能的两个环节。
-
重排(Reflow): 当 DOM 结构发生改变(添加、删除元素)、元素的位置或尺寸发生改变(改变边距、填充、边框、宽度、高度等)、内容发生改变(文本改变、图片被替换等)、页面初始化、浏览器窗口尺寸改变等情况下,浏览器需要重新计算元素的几何属性,并重新构建渲染树。这个过程就是重排。重排会影响到整个页面或部分页面,因为一个元素的改变可能导致其他元素的位置和尺寸发生改变。
-
重绘(Repaint): 当元素的样式发生改变,但不会影响到其在文档流中的位置和尺寸时(例如,改变颜色、背景色、visibility等),浏览器不需要重新计算元素的几何属性,只需要重新绘制元素。这个过程就是重绘。重绘的开销比重排小。
触发重排的操作会导致重绘,而触发重绘的操作不一定导致重排。
以下是一些常见的导致重排的操作:
| 操作 | 描述 |
|---|---|
| DOM 操作 | 添加、删除、修改 DOM 节点 |
| CSS 操作 | 修改元素的几何属性(width, height, margin, padding, border, position, display, etc.) |
| 内容改变 | 修改文本内容,替换图片 |
| 页面初始化 | 首次加载页面 |
| 窗口尺寸改变 | 浏览器窗口大小改变 |
| offsetWidth/Height | 获取元素的 offsetWidth, offsetHeight, clientWidth, clientHeight, scrollTop, scrollLeft 等属性 |
| getComputedStyle() | 调用 getComputedStyle() 方法获取元素的样式信息 |
transform 和 opacity:绕过布局阶段
transform 和 opacity 属性是 CSS 中非常重要的两个属性,它们可以改变元素的外观,而不会触发重排,从而提高性能。这是因为 transform 和 opacity 的修改发生在合成(Composite)阶段,而不是布局(Layout)阶段。
-
transform:transform属性允许你旋转、缩放、倾斜或平移元素。当使用transform改变元素的位置或尺寸时,浏览器不会重新计算元素的几何属性,而是直接在合成阶段对元素进行变换。这意味着transform不会触发重排,只会触发重绘和合成。 -
opacity:opacity属性用于设置元素的不透明度。当改变opacity的值时,浏览器也不会重新计算元素的几何属性,而是直接在合成阶段改变元素的透明度。这意味着opacity不会触发重排,只会触发重绘和合成。
代码示例:比较 transform 和 left/top
为了更直观地理解 transform 和 left/top 的区别,我们来看一个简单的示例:
<!DOCTYPE html>
<html>
<head>
<title>Transform vs. Left/Top</title>
<style>
.box {
width: 100px;
height: 100px;
background-color: red;
position: absolute;
}
.box-transform {
width: 100px;
height: 100px;
background-color: blue;
position: absolute;
}
</style>
</head>
<body>
<div class="box" id="box-left-top">Left/Top</div>
<div class="box-transform" id="box-transform">Transform</div>
<script>
const boxLeftTop = document.getElementById('box-left-top');
const boxTransform = document.getElementById('box-transform');
function moveLeftTop(distance) {
boxLeftTop.style.left = distance + 'px';
boxLeftTop.style.top = distance + 'px';
}
function moveTransform(distance) {
boxTransform.style.transform = `translate(${distance}px, ${distance}px)`;
}
// 模拟动画
let distance = 0;
setInterval(() => {
distance += 1;
moveLeftTop(distance);
moveTransform(distance);
}, 16); // 大约 60 FPS
</script>
</body>
</html>
在这个示例中,我们创建了两个 div 元素,分别使用 left/top 和 transform 来移动它们。在 JavaScript 代码中,我们使用 setInterval 函数来模拟动画,每隔 16 毫秒移动一次元素。
虽然从视觉效果上看,这两个 div 元素的移动效果是一样的,但它们的性能却有很大的差异。使用 left/top 会触发重排和重绘,而使用 transform 只会触发重绘和合成。
如何验证?
可以使用浏览器的开发者工具来验证这一点。打开 Chrome 开发者工具,选择 "Performance" 面板,然后点击 "Record" 按钮,运行示例代码一段时间,然后停止录制。在 Performance 面板中,你可以看到详细的性能分析报告,包括重排和重绘的次数和时间。你会发现使用 left/top 的 div 元素触发了更多的重排。
创建新的合成层(Compositing Layer)
虽然 transform 和 opacity 可以避免重排,但它们仍然会触发重绘。为了进一步优化性能,我们可以将元素提升到新的合成层(Compositing Layer)。
什么是合成层?
合成层是浏览器渲染引擎中的一个概念,它将页面分成多个独立的图层,每个图层可以独立地进行绘制和合成。当元素被提升到新的合成层时,它的绘制操作将不会影响到其他图层,从而减少重绘的范围。
如何创建新的合成层?
以下是一些常见的创建新的合成层的方法:
- 使用
transform属性 - 使用
opacity属性(当opacity的值小于 1 时) - 使用
<video>和<iframe>元素 - 使用
will-change属性
will-change 属性
will-change 属性是一个 CSS 属性,它可以提前告知浏览器,元素可能会发生哪些改变。浏览器可以根据 will-change 属性来优化元素的渲染,例如,将元素提升到新的合成层。
will-change 属性的常用值包括:
transformopacitytopleftscroll-positioncontentsallcustom-ident
注意事项:
过度使用 will-change 可能会导致性能问题。因为创建新的合成层会占用额外的内存和 GPU 资源。只有当元素确实需要进行频繁的改变时,才应该使用 will-change 属性。
示例:使用 will-change 优化 transform 动画
<!DOCTYPE html>
<html>
<head>
<title>Will-Change Example</title>
<style>
.box {
width: 100px;
height: 100px;
background-color: green;
position: absolute;
will-change: transform; /* 告诉浏览器,该元素可能会发生 transform 改变 */
}
</style>
</head>
<body>
<div class="box" id="box-will-change">Will-Change</div>
<script>
const boxWillChange = document.getElementById('box-will-change');
function moveTransform(distance) {
boxWillChange.style.transform = `translate(${distance}px, ${distance}px)`;
}
// 模拟动画
let distance = 0;
setInterval(() => {
distance += 1;
moveTransform(distance);
}, 16); // 大约 60 FPS
</script>
</body>
</html>
在这个示例中,我们为 div 元素添加了 will-change: transform 属性。这意味着浏览器会提前将该元素提升到新的合成层,从而进一步优化 transform 动画的性能。
总结:避免重排,优化重绘
通过使用 transform 和 opacity 属性,我们可以避免重排,减少重绘的范围,从而提高网页的性能。 通过will-change属性,提前告知浏览器元素的改变,可以进一步提升性能。但是,过度使用 will-change 可能会导致性能问题,所以需要谨慎使用。
更多IT精英技术系列讲座,到智猿学院