重绘和回流

写在前面

浏览器内核是指支持浏览器运行的最核心的程序,分为两个部分,一个是渲染引擎,一个是 JS 引擎,渲染引擎在不同内核的浏览器中也不是都相同的。下面来介绍下浏览器渲染过程:

  1. 解析html/css/js: 浏览器会把 HTML 解析成1个 DOM Tree,把 CSS 解析成 CSSOM,也就是 CSS 规则树,类似于 DOM 结构,加载 js 后来操作 DOM Tree和 CSS Rule Tree。
  2. 构造 Render Tree: 解析完成后,渲染引擎会通过 DOM Tree 和 Css Rule Tree 来构造 Render Tree(不包括 display: none、head,包括 visibility: hidden)。渲染树只有需要显示的节点和这些节点的样式信息。如遇到 js 会停止渲染,控制权交给 js 引擎执行 js 代码(js 的加载和解析与执行会阻塞 DOM 的构建),因为浏览器渲染和 js 执行共用一个线程。
  3. 布局:生成渲染树后,浏览器就会根据渲染树来布局(也可以叫回流),这个阶段浏览器会弄清楚每个节点在页面中确切的位置和大小。布局流程会输出一个盒模型,包括每个元素在视口确切的位置和尺寸,所有相对测量值都会转换为屏幕上的绝对元素。
  4. 绘制: 布局完成后,浏览器会立即发出 ”Paint Setup“ 和 ”Paint“ 事件,将渲染树转换为屏幕上的像素。

在这里不对浏览器渲染过程做过多的赘述,具体更深入过程可见 你不知道的浏览器页面渲染机制

什么是重绘和回流(rellow 和 repaint)

  • 回流指浏览器为了重新渲染部分或全部文档而重新计算文档中元素的位置几何结构的过程,引起回流的行为有改变浏览器窗口的大小、使用 js 对 DOM 元素的添加、删除、改变其 class 等等,也就是当页面元素的布局和几何属性改变后,重新计算元素的几何属性,然后再将计算的结果绘制出来这个过程就是回流,所以回流一定会引起重绘,重绘不一定引起回流。例如堆积木,抽掉中间一块,为了不脱节,必须将断点重新结合起来,这个结合梳理的过程就叫回流。
  • 重绘就如其字面意思重新绘制,类似于绘图画画,比如修改某个元素的背景色,这些影响元素外观、风格而不会影响布局的操作将会引起重绘。浏览器不需要冲洗计算元素的几何属性、直接为改元素绘制新的样式即可。

当网页生成的时候,至少会渲染一次。在用户访问的过程中,还会不断重新渲染。重新渲染会重复回流+重绘或者只有重绘。回流必定会发生重绘,重绘不一定会引发回流。重绘和回流会在我们设置节点样式时频繁出现,同时也会很大程度上影响性能。回流所需的成本比重绘高的多,改变父节点里的子节点很可能会导致父节点的一系列回流。

重绘回流何时发生

回流:

  1. 调整窗口大小
  2. 改变字体
  3. 增加或者移除样式表
  4. 内容变化,比如用户在input框中输入文字
  5. 激活 CSS 伪类,比如 :hover
  6. 操作 class 属性
  7. 脚本操作 DOM
  8. 计算 offsetWidth 和 offsetHeight 属性
  9. 设置 style 属性的值

重绘:

  1. 改变 color
  2. 改变 background
  3. 改变 visibility
  4. 改变text-decoration
  5. 改变background-image position repeat color size
  6. 改变outline
  7. 改变border-radius
  8. 改变box-shadow

如何降低它们对性能的影响

  1. 如果想设定元素的样式,通过改变元素的 class 名 (尽可能在 DOM 树的最末端),尽可能在DOM树的里面改变class,可以限制了回流的范围,使其影响尽可能少的节点
  2. 避免设置多层内联样式,因为每个都会造成回流,样式应该合并在一个外部类,这样当该元素的class属性可被操控时仅会产生一个reflow。
  3. 动画效果应用到position属性为absolute或fixed的元素上,它们不影响其他元素的布局,所它他们只会导致重新绘制,而不是一个完整回流。这样消耗会更低。
  4. 牺牲平滑度换取速度,Opera还建议我们牺牲平滑度换取速度,其意思是指您可能想每次1像素移动一个动画,但是如果此动画及随后的回流使用了100%的CPU,动画就会看上去是跳动的,因为浏览器正在与更新回流做斗争。动画元素每次移动3像素可能在非常快的机器上看起来平滑度低了,但它不会导致CPU在较慢的机器和移动设备中抖动。
  5. 避免使用table布局,可能很小的一个小改动会造成整个 table 的重新布局
  6. CSS 选择符从右往左匹配查找,避免节点层级过多
  7. 将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点。比如对于 video 标签来说,浏览器会自动将该节点变为图层。
  8. 减少不必要的DOM深度。因为无论你改变DOM节点树上任何一个层级都会影响节点树的每个层级——从根结点一直到修改的子节点。不必要的节点深度将导致执行回流时花费更多的时间。

浏览器优化

现代浏览器会对频繁的回流或重绘操作进行优化:
浏览器会维护一个队列,把所有引起回流和重绘的操作放入队列中,如果队列中的任务数量或者时间间隔达到一个阈值的,浏览器就会将队列清空,进行一次批处理,这样可以把多次回流和重绘变成一次。
当你访问以下属性或方法时,浏览器会立刻清空队列:

clientWidth、clientHeight、clientTop、clientLeft、offsetWidth、offsetHeight、offsetTop、offsetLeft、scrollWidth、scrollHeight、scrollTop、scrollLeft、width、height、getComputedStyle()、getBoundingClientRect()。
因为队列中可能会有影响到这些属性或方法返回值的操作,即使你希望获取的信息与队列中操作引发的改变无关,浏览器也会强行清空队列,确保你拿到的值是最精确的。

参考文章

回流与重绘:CSS性能让JavaScript变慢?
你不知道的浏览器页面渲染机制
浏览器的回流与重绘 (Reflow & Repaint)