性能优化——把重排、重绘、合成掰开了、揉碎了学

引言

在前端开发中,有重排(reflow)、重绘(repaint)和合成(compositing)这三个在代码中无处不在,却鲜有人提起的概念,其实这是一名前端进阶道路上绕不开的一道题。

这三个概念,仿佛是渲染引擎的交织舞台,影响着页面的构建、外观和性能。它们如同隐藏在幕布背后的舞者,相互合作,决定了用户的视觉和交互体验。在掌握了它们的本质后,我们将能够优化页面、提升性能,创造出更为流畅和令人惊艳的用户界面。

在接下来的内容中,我们将深入探讨重排、重绘和合成的原理,领略它们在前端开发中的不可或缺性,以及一些优化技巧和最佳实践。

重排(Reflow)

什么是重排、何时重排

重排是指当页面的布局和几何属性发生变化时,浏览器需要重新计算元素的位置和大小。这个过程涉及到整个文档树的重新布局,因此是非常耗费性能的操作。一些可能引起重排的操作包括改变元素的尺寸、位置、内容等。频繁的重排会导致页面的卡顿和响应时间延长。

常见导致重排的操作和行为

  • 添加/删除可见的DOM元素
  • 修改元素位置属性,如left、top、right等
  • 页面初始加载时,以及响应式布局变化时
  • 改变页面布局,如宽高、内外边距、浮动等
  • 改变字体
  • 滚动页面

那么我们可以怎么确认是否触发重排呢?

先来一个demo

<!DOCTYPE html>
<html lang="en">
 
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
 
<body>
  <div id="box" style="width: 300px; height: 300px; background-color: #bbb"></div>
  <input type="button" id="btn" value="点击按钮">
</body>
 
<script>
  // 示例1:修改元素宽高
  const div = document.getElementById('box');
 
  function resize() {
    div.style.width = '50%';
    div.style.height = '10vh';
  }
 
  // 点击按钮修改div大小,会触发重排
  document.getElementById('btn').addEventListener('click', resize);
</script>
 
</html>

在浏览器打开html文件,打开chrome devtools 的permance,开始录制,然后点击按钮 - 缩放窗口,再停止录制。会看到这样的界面,可以选择时间范围以更细致分析:

reflow1.png

看到layout就说明浏览器进行重排了,变换时间范围,会发现layout时间不一样,这就是缩放过程中布局不断更新,所以不断触发重排。

优化技巧

  • 避免频繁访问布局属性,尽量一次性获取或修改

    // 如上面的demo可以修改resize为
    function resize() {
    	div.style.cssText = 'width: 50%; height: 10vh; background-color: #bbb';
    }
  • 使用 CSS3 动画替代 JavaScript 动画,因为前者通常能利用合成来避免重排

  • 将频繁变动的元素设置为绝对定位或固定定位,脱离文档流,以减少对其他元素布局的影响

  • 使用虚拟DOM,减少实际DOM操作

重绘(Repaint)

什么是重绘,何时重绘

重绘是指当元素的外观属性(如颜色、背景等)发生变化时,浏览器会重新绘制这些元素。与重排不同,重绘不会改变元素的布局,只是改变元素的外观。尽管重绘的代价相对较小,但仍然需要消耗一定的性能。

常见导致重排的操作和行为

  • 改变颜色、背景、阴影等视觉样式
  • 设置文本内容或字体样式
  • 单独使用transform、opacity来实现动画效果

你可以修改上面的demo来验证,permance里的paint就是重绘。

repaint1.png

优化技巧

  • 合并多次样式改变,避免重复重绘
  • 使用CSS3的transform和opacity属性,它们能够在不引起重排的情况下改变元素外观
  • 对动画使用will-change提高合成层创建效率

合成(Compositing)

什么是合成,何时合成

合成将多个图层合成为最终的屏幕显示。现代浏览器利用硬件加速来进行合成,即利用计算机的GPU来加速图层的合成过程。合成的优化可以显著提高页面的渲染性能,并且减少对CPU的压力

启用合成层的一些方法

  • 使用硬件加速,将部分图层委托给 GPU 处理,加快图层合成速度

  • 利用图层的概念,将独立于布局的元素分层,使得只有特定图层的变化会触发合成,而不会引发重排和重绘

  • 使用 will-change 属性明确声明哪些属性将会发生变化,帮助浏览器进行优化

    .box {
      will-change: transform;
    }
     
    .box:hover {
      transform: scale(1.1); 
    }
  • 对3D transform进行动画

    .box {
      animation: rotate 1s linear infinite;
    }
     
    @keyframes rotate {
      100% { 
        transform: rotateY(360deg); 
      }
    }
  • 对元素设置opacity小于1

    .box {
      opacity: 0.5;
    }
  • 对元素设置video、canvas、WebGL等标签

总结来说,能用CSS的地方就别用JS,这是用合成层优化重排、重绘的根本。

总结

在前端开发的世界中,理解重排、重绘和合成的概念是我们优化性能的重要一环。这些平时工作中极少提起的名词,实际上是良好的用户体验的关键所在。

前端er应该不再把性能优化视为一项任务,而要看作是一种创造力的表达。就像艺术家将每一笔划痕都视为构建完美画作的一部分,我们也能通过优化网页的每一帧,创造出更极致的前端体验。

参考资料