回流和重绘想必都听过,每当说起性能优化的时候,都会聊到这个话题,遂记录下,以加深下记忆,希望之后再探讨时能想起一些细节而不至于事后再去网上搜索,同时也希望能够让正在看的你对回流和重绘有一些思考。
💭本文首发掘金: 简单记录下回流 reflow 和重绘 repaint
浏览器引擎渲染过程
开始之前先熟悉下浏览器渲染 DOM 的一个大致过程,主要分了四步:
解析 HTML 结构并构建出 DOM 树
构建 render 树
布局 render 树
绘制 render 树
详细的过程如下:
-
分析
HTML结构,构建出DOM树 -
分析
CSS样式,解析出样式规则表 -
将
DOM树和样式规则表关联,从而构建出 render 树(构建的过程称之为Attachment)-
从 DOM 树的根节点开始,遍历每一个可见的节点
-
head, meta, title 等节点会被忽略,不会被添加到
render树中,同样不影响渲染的输出 -
样式为
display: none同样也被忽略【1】
-
-
对每一个可见的节点,找到并匹配相对应的样式规则
-
-
开始布局
render树上的节点(也就是确定出每个节点的坐标和大小) -
有了坐标后,浏览器就会遍历
render树,进而绘制出各个节点
注: 【1】 区别于
visibility: hidden,visibility: hidden会将元素设置为不可见,同时占据页面上的一块空间,而display: none则是将节点从整个 render 树中移除,可以将其理解成不是布局中的一部分。
什么是回流和重绘
在刚刚看到的绘制 render 树的过程中,可能会发生的两种渲染行为,我们分别称之为回流,重绘。
回流(reflow)
当 DOM 节点的几何形状发生变化时,(就比如说 DOM 节点的宽高发生了变化)
浏览器将检查所有其他任何受影响的区域,并重新计算其尺寸和位置
再自动排列 DOM 节点,最后再重新绘制,这个过程称为回流/重排(reflow)。
可以理解为 DOM 整容了,就会触发回流(reflow)
重绘(repaint)
若 DOM 节点的图像、颜色和阴影发生了变化,即不涉及任何排版、布局问题时,则跳过布局,直接绘制,这个过程称为重绘(repaint)。
可以理解为 DOM 只是简单的化妆了,才会触发重绘(repaint)
触发场景
回流(reflow)
- js 操作 DOM 时
- 设置 style 属性 / 改变元素可见性(
display: none) - 元素
width/height、fontSize、border的修改 animation和transition- 读取了元素自身的某些属性(
offsetWidth,offsetHeight,getComputedStyle() 等 ) scroll滚动事件- 窗口大小的改变
重绘(repaint)
- 颜色的修改
- 文本方向的修改
- 阴影的修改
结论
reflow的触发会重新计算节点的尺寸和位置,而且还有可能触发其children节点、parent节点和其他节点的reflow,开销较大repaint不一定会触发reflow,但是reflow必然导致repaint,- 由于
repaint不会影响布局,所以较于reflow来说开销很低
实际上,浏览器可以选择等到线程结束后再重新回流并修改 DOM。这意味着,如果这些修改在同一个线程中足够快地发生了,那么它们可能只会产生一个 reflow
也就是说,大部分现代浏览器都已经对
reflow做了一些优化,它会等到足够多的数量时做一次批处理,再来触发reflow可以看这篇文章详细讲解了内部的细节 Keeping the number of reflows to a minimum
一些优化
- 尽量减少
DOM操作 - 减少
DOM嵌套层级 - 尽量不使用内联样式
- 避免使用
table进行布局,table中每个元素的大小以及内容的改动,都会导致整个table的重新计算。改用div可有效避免不必要的reflow和repaint - 对于那些复杂的动画,对其设置
position: fixed/absolute,尽可能地使元素脱离文档流,从而减少对其他元素的影响
参考资料:
Render-tree Construction, Layout, and Paint