重绘与回流

在面试的时候被问到重绘(Repaint)与回流(Reflow),虽然自己之前有了解过,但是也是有印象,所以这次去网上查了资料,好好总结下。

虽然在 PC 端,重绘与回流对性能的影响微乎其微,但是在移动端,这两者简直是性能杀手。

浏览器解析过程

首先我们要明白的是浏览器的解析过程,才能更好地理解重绘与回流。解析过程大致分为以下四步:

  1. 解析 HTML 以构建 DOM 树
    渲染引擎开始解析 HTML 文档,转换树中的 HTML 标签到 DOM 节点。生成内容树。
  2. 构建渲染树
    解析 CSS 成样式结构体,根据 CSS 选择器计算出节点的样式,创建另一个树:渲染树
  3. 布局渲染树
    从根节点递归调用,计算每一个元素的大小、位置等,给每个节点所应该出现在屏幕上的精确坐标。
  4. 绘制渲染树
    遍历渲染树,每个节点将使用 UI 后端层来绘制。

    渲染树(render tree)的特性

    渲染树能识别样式,每个节点都有自己的样式,而且渲染树不包含隐藏的节点(比如 display: none 的节点以及 <head></head> 节点),因为这些节点不会用于呈现,而且不会影响呈现,所以就不包含在渲染树中。

    注意: visility: hidden 会影响布局(layout),会占据空间。

重绘与回流的定义

我们可以看到重绘与回流分别出现在了第三和第四步。

对于 DOM 结构中的各个元素都有自己的盒子(模型),这些都需要浏览器根据各种样式(浏览器的、开发人员定义的等等)来计算并根据计算结果将元素放到它该出现的位置,这个过程称之为回流(reflow)

当各种盒子的位置、大小以及其他属性,例如颜色、字体大小等都确定下来之后,浏览器便把这些元素都按照格子的特性绘制了一遍,于是页面的内容出现了。这个过程就是重绘(repaint)

我们可以通过一个例子来更好地理解。

一棵树,四季变化就会引起树叶颜色的变化,这个就是重绘。若是将树枝砍断后,这棵树等到重新长出新树枝恢复到以前的样子,这个过程就类似回流。

  1. 当渲染树中的一部分(或者全部)因为元素的规模尺寸、布局、隐藏等改变而需要重新构建。就是回流(reflow)。每个页面在第一次加载的时候就会产生一次回流。在回流的时候,浏览器会使渲染树中受到影响的部分失效,并且重新构造这部分渲染树,完成回流后,浏览器会重新绘制受到影响的部分,产生重绘。
  2. 当渲染树中的一些元素需要更新属性,而这些属性只是影响元素的外观、风格,例如 background-color,而不会影响布局。则只会产生重绘。

回流必将引起重绘,重绘不一定会产生回流。

产生重绘及回流的一些操作

回流的成本比重绘高得多。DOM 树里每个节点都有 reflow 方法,一个节点的 reflow 很有可能导致子节点,甚至父节点以及同级的节点产生 reflow。

引起repaint的操作

一个元素的外观改变,但是没有改变布局的情况

  1. visibility
  2. outline
  3. background color

引起reflow的操作

  1. 改变窗口大小(resize)
  2. 改变字体
  3. 增加和删除样式表
  4. 用户的交互(比如内容的改变,用户在输入框输入文字,激活伪类,类似 :hover 这种)
  5. 操作 class 属性
  6. 脚本操作 DOM
  7. 读取元素的某些属性(offsetLeft、offsetTop、offsetHeight、offsetWidth、 scrollTop/Left/Width/Height、clientTop/Left/Width/Height、 getComputedStyle()、currentStyle(in IE))
  8. 设置 style 属性
  9. CSS3 动画(animation)和过渡(transition): 动画的每一 frame 都会触发 Reflow。

优化建议

  1. 尽可能少的修改元素 style 上的属性
  2. 尽量通过 className 来修改样式
  3. 集中修改 CSS 样式,多项改变只在最后才更新到界面,例如将元素先设置为 display: none,修改完成后再改回原来的值
  4. 缓存 layout 属性值(var offsetL = elem.offsetLeft)
  5. 设置元素的 position 属性为 absolutefixed
  6. 避免使用 table 布局或 内联样式(inline style)
  7. 避免使用 CSS 里的 JS 表达式(expression)
  8. 减少 DOM 的层级