在面试的时候被问到重绘(Repaint)与回流(Reflow),虽然自己之前有了解过,但是也是有印象,所以这次去网上查了资料,好好总结下。
虽然在 PC 端,重绘与回流对性能的影响微乎其微,但是在移动端,这两者简直是性能杀手。
浏览器解析过程
首先我们要明白的是浏览器的解析过程,才能更好地理解重绘与回流。解析过程大致分为以下四步:
- 解析 HTML 以构建 DOM 树
渲染引擎开始解析 HTML 文档,转换树中的 HTML 标签到 DOM 节点。生成内容树。 - 构建渲染树
解析 CSS 成样式结构体,根据 CSS 选择器计算出节点的样式,创建另一个树:渲染树 - 布局渲染树
从根节点递归调用,计算每一个元素的大小、位置等,给每个节点所应该出现在屏幕上的精确坐标。 - 绘制渲染树
遍历渲染树,每个节点将使用 UI 后端层来绘制。渲染树(render tree)的特性
渲染树能识别样式,每个节点都有自己的样式,而且渲染树不包含隐藏的节点(比如
display: none
的节点以及<head></head>
节点),因为这些节点不会用于呈现,而且不会影响呈现,所以就不包含在渲染树中。注意:
visility: hidden
会影响布局(layout),会占据空间。
重绘与回流的定义
我们可以看到重绘与回流分别出现在了第三和第四步。
对于 DOM 结构中的各个元素都有自己的盒子(模型),这些都需要浏览器根据各种样式(浏览器的、开发人员定义的等等)来计算并根据计算结果将元素放到它该出现的位置,这个过程称之为回流(reflow)。
当各种盒子的位置、大小以及其他属性,例如颜色、字体大小等都确定下来之后,浏览器便把这些元素都按照格子的特性绘制了一遍,于是页面的内容出现了。这个过程就是重绘(repaint)。
我们可以通过一个例子来更好地理解。
一棵树,四季变化就会引起树叶颜色的变化,这个就是重绘。若是将树枝砍断后,这棵树等到重新长出新树枝恢复到以前的样子,这个过程就类似回流。
- 当渲染树中的一部分(或者全部)因为元素的规模尺寸、布局、隐藏等改变而需要重新构建。就是回流(reflow)。每个页面在第一次加载的时候就会产生一次回流。在回流的时候,浏览器会使渲染树中受到影响的部分失效,并且重新构造这部分渲染树,完成回流后,浏览器会重新绘制受到影响的部分,产生重绘。
- 当渲染树中的一些元素需要更新属性,而这些属性只是影响元素的外观、风格,例如
background-color
,而不会影响布局。则只会产生重绘。
回流必将引起重绘,重绘不一定会产生回流。
产生重绘及回流的一些操作
回流的成本比重绘高得多。DOM 树里每个节点都有 reflow 方法,一个节点的 reflow 很有可能导致子节点,甚至父节点以及同级的节点产生 reflow。
引起repaint的操作
一个元素的外观改变,但是没有改变布局的情况
- visibility
- outline
- background color
引起reflow的操作
- 改变窗口大小(resize)
- 改变字体
- 增加和删除样式表
- 用户的交互(比如内容的改变,用户在输入框输入文字,激活伪类,类似 :hover 这种)
- 操作 class 属性
- 脚本操作 DOM
- 读取元素的某些属性(offsetLeft、offsetTop、offsetHeight、offsetWidth、 scrollTop/Left/Width/Height、clientTop/Left/Width/Height、 getComputedStyle()、currentStyle(in IE))
- 设置 style 属性
- CSS3 动画(animation)和过渡(transition): 动画的每一 frame 都会触发 Reflow。
优化建议
- 尽可能少的修改元素 style 上的属性
- 尽量通过 className 来修改样式
- 集中修改 CSS 样式,多项改变只在最后才更新到界面,例如将元素先设置为
display: none
,修改完成后再改回原来的值 - 缓存 layout 属性值(var offsetL = elem.offsetLeft)
- 设置元素的
position
属性为absolute
或fixed
- 避免使用 table 布局或 内联样式(inline style)
- 避免使用 CSS 里的 JS 表达式(expression)
- 减少 DOM 的层级