0%

回流和重绘

在搞清楚回流和重绘的概念之前,我们要清楚浏览器的渲染过程。

渲染主流程

渲染引擎首先通过网络获得所请求文档的内容,通常以8K分块的方式完成。下面是渲染引擎在取得内容之后的基本流程:

解析html以构建dom树 -> 构建render树 -> 布局render树 -> 绘制render树

渲染引擎基本流程

webkit渲染引擎主流程:

webkit渲染引擎主流程

  • DOM Tree:浏览器将HTML解析成树形的数据结构;
  • CSS Rule Tree:浏览器将CSS解析成树形的数据结构;
  • Render Tree: DOM和CSSOM合并后生成Render Tree;
  • Layout: 有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系,从而去计算出每个节点在屏幕中的位置;
  • painting: 按照算出来的规则,通过显卡,把内容画到屏幕上。

更详细一点来说:

  1. 浏览器会将 HTML 解析成一个 DOM 树,DOM 树的构建过程是一个深度遍历过程:当前节点的所有子节点都构建好后才会去构建当前节点的下一个兄弟节点。

  2. 将 CSS 解析成 CSS Rule Tree

  3. 根据 DOM 树和 CSSOM 来构造 Rendering Tree注意:Rendering Tree 渲染树并不等同于 DOM 树,因为一些像 Header 或 display:none 的东西就没必要放在渲染树中了。

  4. 有了 Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的 CSS 定义以及他们的从属关系。下一步操作称之为 layout,顾名思义就是计算出每个节点在屏幕中的位置。

  5. 再下一步就是绘制,即遍历render树,并使用UI后端层绘制每个节点。

注意:上述这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html都解析完成之后再去构建和布局render树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。

回流和重绘

reflow(回流):

  • 当浏览器发现某个部分发生了点变化影响了布局,需要倒回去重新渲染,内行称这个回退的过程叫 reflow。
  • reflow 会从 <html> 这个 root frame 开始递归往下,依次计算所有的结点几何尺寸和位置。
  • reflow 几乎是无法避免的。现在界面上流行的一些效果,比如树状目录的折叠、展开(实质上是元素的显 示与隐藏)等,都将引起浏览器的 reflow。鼠标滑过、点击……只要这些行为引起了页面上某些元素的占位面积、定位方式、边距等属性的变化,都会引起它内部、周围甚至整个页面的重新渲染。
  • 通常我们都无法预估浏览器到底会 reflow 哪一部分的代码,它们都彼此相互影响着。

repaint(重绘):

改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,但是元素的几何尺寸没有变。

通过上诉我们知道:回流必定引发重绘,重绘不一定引发回流。回流的代价比重绘高

哪些会引起回流和重绘

回流:

  • DOM的添加和删除;
  • 页面的加载;
  • 元素尺寸改变——边距、填充、边框、宽度和高度;
  • 元素位置的改变;
  • 内容变化,比如用户在input框中输入文字;
  • 浏览器窗口尺寸改变——resize事件发生时;

重绘:

color、border-style、visibility、background、text-decoration、outline、border-radius、box-shadow等

如何减少回流和重绘

  • 使用 transform 替代 top
  • 使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局)
  • 不要把节点的属性值放在一个循环里当成循环里的变量。
  • 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局
  • 动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用 requestAnimationFrame
  • CSS 选择符从右往左匹配查找,避免节点层级过多
  • 将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点。比如对于 video 标签来说,浏览器会自动将该节点变为图层。

浏览器的回流优化机制:浏览器会维护1个队列,把所有会引起重排、重绘的操作放入这个队列,等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会flush队列,进行一个批处理。这样就会让多次的重排、重绘变成一次重排重绘。

但是,有些时候你可能会(经常是不知不觉)强制刷新队列并要求计划任务立即执行。获取布局信息的操作会导致队列刷新。因此,尽量不要在修改样式或者布局信息时查询样式,因为查询的时候会强制重排,导致浏览器无法优化多次重排。

使用绝对位置定位页面上的动画元素,将其脱离文档流,可以有效的防止重排。比如有时候做动画特效时,我们通过设置 position:absolute 可以有效的减少重排。

transform是否可以避免重排重绘问题

CSS的最终表现分为以下四步:Recalculate Style -> Layout -> Paint Setup and Paint -> Composite Layers

按照中文的意思大致是 查找并计算样式 -> 排布 -> 绘制 -> 组合层

这上面的几个步骤有点类似于上文说到的重排必定导致重绘,而查询属性会强制发生重排。所以上文提到的重排重绘内容可以结合这里进行理解。

由于 transform 是位于 Composite Layers 层,而 width、left、margin 等则是位于 Layout 层,在 Layout 层发生的改变必定导致 Paint Setup and Paint -> Composite Layers,所以相对而言使用 transform 实现的动画效果肯定比left这些更加流畅。

而且就算抛开这一角度,在另一方面浏览器也会针对 transform 等开启 GPU 加速。