愿你坚持不懈,努力进步,进阶成自己理想的人

—— 2017.09, 写给3年后的自己

理解reflow和repaint

一、浏览器的绘制过程

  • 浏览器首先将获取得到的HTML文档解析为DOM树,HTML中的每一个标签都是DOM树中的一个节点。DOM树里包含了所有的HTML标签(包括display: none隐藏的标签、JS动态添加的标签)
  • 浏览器把所有的样式解析为CSSOM样式结构体,解析过程中会去掉当前浏览器不能识别的样式
  • DOM和CSSOM组合后,构建Render Tree(呈现树),Render Tree类似于DOM树,但是Render Tree能够识别样式,每个Node都有自己的style,并且Render Tree不会包含隐藏的节点(display:none这种不占据空间的节点,而非visiable: hidden这类占据空间的节点),仅仅会包含用于呈现的节点。树中的每个节点,都可以称为Box
  • 浏览器根据Render Tree来绘制页面


二、reflow(回流)和repaint(重绘)

1、当render tree中有元素改变了尺寸、大小、布局等会影响到其他元素布局的属性时,render tree就需要重新构建,而重新构建的这一个过程,便称为reflowreflow时,浏览器会使得受影响的部分失效,并重新进行构建,完成reflow后,浏览器重新绘制受影响的部分到屏幕中
2、如果render tree中的一些属性,仅仅是发生元素外观、风格上的改变,而不造成布局上的改变的时候,就只会发生repaint。如:colorbackground-color之类的改变
注意: reflow必将引起repaint,而repaint不一定会引起reflow


三、发生reflow的时机

1、添加、删除可见DOM元素
2、元素位置改变
3、元素尺寸改变:margin、padding、border、width、height
4、内容改变:如img的width、height变化
5、页面渲染初始化
6、浏览器窗口改变
示例:

var b = document.body.style;
b.padding = "10px";              // 发生reflow+repaint
b.border  = "1px solid #EEE";    // 发生reflow+repaint
b.color   = "#F00";              // 发生repaint

document.body.appendChild(document.createTextNode('Hello, world!'));
// reflow+repaint


四、浏览器的优化

1、浏览器为了避免每次做一次DOM布局的改变就引起一次reflow从而造成巨大的资源消耗,使用一个队列来延迟执行。浏览器会把那些可能引起reflow、repaint的操作加入队列中,等队列中有一定的元素数量或者到了某一时间间隔后,就flush队列,从而使得多次的reflow/repaint成为一次reflow/repaint
2、但是,有时候我们需要获取一些信息的时候,浏览器不得不进行flush,以确保我们获得的是准确的值,如获取以下值的时候:

  • offsetTop、offsetLeft、offsetWidth、offetHeight
  • scrollTop、scrollLeft、scrollWidth、scrollHeight
  • clientTop、clientLeft、clientWidth、clientHeight
  • width、height
  • 调用getComputedStyle()或者IE的currentStyle


五、如何减少reflow、repaint

1、直接改变className。或者使用cssText来一次性改变多个属性,如:

e.style.cssText = "width: 100px; height: 100px";
e.className = "someClassName";

2、让要操作的元素进行离线处理,处理完后一起更新

  • 使用DocumentFragment进行缓存操作,引发一次reflow和repaint
  • display: none,引发两次reflow和repaint
  • 使用cloneNodereplaceChild,引发一次reflow和repaint

3、不要经常访问会引起flush队列的属性(即不要经常访问offsetTop这类的属性)
4、让元素脱离动画流,减少回流的Render Tree规模


参考资料

http://www.css88.com/archives/4996