浏览器重排(Reflow)与重绘(Repaint)的边界:利用`transform`与`opacity`实现零布局开销

浏览器重排(Reflow)与重绘(Repaint)的边界:利用transformopacity实现零布局开销

大家好,今天我们来深入探讨浏览器渲染引擎中的两个关键概念:重排(Reflow)和重绘(Repaint),以及如何利用 transformopacity 属性来优化性能,实现“零布局开销”。

渲染引擎的工作流程

在深入探讨重排和重绘之前,我们需要了解浏览器渲染引擎的基本工作流程。当浏览器接收到 HTML、CSS 和 JavaScript 代码后,会经历以下几个关键步骤:

  1. 解析 HTML 构建 DOM 树(DOM Tree): 浏览器解析 HTML 代码,构建一个树状结构,代表网页的结构。每个 HTML 元素对应 DOM 树中的一个节点。

  2. 解析 CSS 构建 CSSOM 树(CSS Object Model): 浏览器解析 CSS 代码,构建 CSSOM 树。CSSOM 包含了所有 CSS 规则,包括外部样式表、内联样式和浏览器默认样式。

  3. 渲染树(Render Tree)构建: 浏览器将 DOM 树和 CSSOM 树结合起来,构建渲染树。渲染树只包含需要显示的节点,以及它们的样式信息。例如,display: none 的元素不会出现在渲染树中。

  4. 布局(Layout,也称 Reflow 或 Layout): 渲染引擎遍历渲染树,计算每个节点在页面上的确切位置和大小。这个过程被称为布局或重排。

  5. 绘制(Paint,也称 Repaint): 渲染引擎遍历渲染树,将每个节点绘制到屏幕上。这个过程被称为绘制或重绘。

  6. 合成(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() 方法获取元素的样式信息

transformopacity:绕过布局阶段

transformopacity 属性是 CSS 中非常重要的两个属性,它们可以改变元素的外观,而不会触发重排,从而提高性能。这是因为 transformopacity 的修改发生在合成(Composite)阶段,而不是布局(Layout)阶段。

  • transform transform 属性允许你旋转、缩放、倾斜或平移元素。当使用 transform 改变元素的位置或尺寸时,浏览器不会重新计算元素的几何属性,而是直接在合成阶段对元素进行变换。这意味着 transform 不会触发重排,只会触发重绘和合成。

  • opacity opacity 属性用于设置元素的不透明度。当改变 opacity 的值时,浏览器也不会重新计算元素的几何属性,而是直接在合成阶段改变元素的透明度。这意味着 opacity 不会触发重排,只会触发重绘和合成。

代码示例:比较 transformleft/top

为了更直观地理解 transformleft/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/toptransform 来移动它们。在 JavaScript 代码中,我们使用 setInterval 函数来模拟动画,每隔 16 毫秒移动一次元素。

虽然从视觉效果上看,这两个 div 元素的移动效果是一样的,但它们的性能却有很大的差异。使用 left/top 会触发重排和重绘,而使用 transform 只会触发重绘和合成。

如何验证?

可以使用浏览器的开发者工具来验证这一点。打开 Chrome 开发者工具,选择 "Performance" 面板,然后点击 "Record" 按钮,运行示例代码一段时间,然后停止录制。在 Performance 面板中,你可以看到详细的性能分析报告,包括重排和重绘的次数和时间。你会发现使用 left/topdiv 元素触发了更多的重排。

创建新的合成层(Compositing Layer)

虽然 transformopacity 可以避免重排,但它们仍然会触发重绘。为了进一步优化性能,我们可以将元素提升到新的合成层(Compositing Layer)。

什么是合成层?

合成层是浏览器渲染引擎中的一个概念,它将页面分成多个独立的图层,每个图层可以独立地进行绘制和合成。当元素被提升到新的合成层时,它的绘制操作将不会影响到其他图层,从而减少重绘的范围。

如何创建新的合成层?

以下是一些常见的创建新的合成层的方法:

  • 使用 transform 属性
  • 使用 opacity 属性(当 opacity 的值小于 1 时)
  • 使用 <video><iframe> 元素
  • 使用 will-change 属性

will-change 属性

will-change 属性是一个 CSS 属性,它可以提前告知浏览器,元素可能会发生哪些改变。浏览器可以根据 will-change 属性来优化元素的渲染,例如,将元素提升到新的合成层。

will-change 属性的常用值包括:

  • transform
  • opacity
  • top
  • left
  • scroll-position
  • contents
  • all
  • custom-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 动画的性能。

总结:避免重排,优化重绘

通过使用 transformopacity 属性,我们可以避免重排,减少重绘的范围,从而提高网页的性能。 通过will-change属性,提前告知浏览器元素的改变,可以进一步提升性能。但是,过度使用 will-change 可能会导致性能问题,所以需要谨慎使用。

更多IT精英技术系列讲座,到智猿学院

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注