要谈Reflow(重排)和Reprint(重绘)总是离不开浏览器解析网页的原理,可以先看下这篇文章:《How browsers work》(中文翻译版,陈浩压缩版)了解下浏览器在背后都做了些什么。
浏览器由很多组件构成,他们都有不同的分工,协同工作把网页渲染出来。其中呈现引擎(Webkit)和Javascript解释器(V8)是我们今天所关注的重点。呈现引擎会尽可能快的完成:把HTML解析为DOM树(同时解析CSS)→同时构造呈现树(Render tree)→布局(Layout)→绘制(Paint)。
其中解析的算法是相当复杂的(尤其对于HTML,因为它的容错性很高),引擎会根据W3C的相关规定,在通过涉及词法分析、语法分析、自动生成解析器、编译等一系列高大上的东西后完成对DOM树的构建。呈现器知道如何通过很多规则和计算来进行布局并将自身及其子元素绘制出来。我们先暂停下,因为到这里有很多需要注意的地方:
- DOM树和呈现树显然不是一样的,呈现树是DOM树的可见部分,部分对应但非一一对应。例如:如果元素display:none它就不会被添加到呈现树中;类似select或是无效的HTML元素会有多个呈现器。浮动和绝对/固定定位的元素在呈现树中的位置和DOM树中的位置不同(脱离文档流)。node节点在呈现树中称为 frame(或是box) 。
- 解析器遇到<script>标记时立即解析并执行脚本。文档的解析将停止,直到脚本执行完毕。如果脚本是外部的,那么解析过程会停止,直到从网络同步抓取资源完成后再继续。可以将脚本标注为defer,这样它就不会停止文档解析,而是等到解析结束才执行。HTML5增加了async属性可将脚本标记为异步。
- Firefox在样式表加载和解析的过程中,会禁止所有脚本。而对于Webkit而言,仅当脚本尝试访问的样式属性可能受尚未加载的样式表影响时,它才会禁止该脚本。
- 由于为DOM匹配样式的计算(Calculate Style,样式优先级等)非常复杂,CSS选择器应该尽可能的简单避免过多层叠。
到目前为止呈现引擎已经完成了前两个步骤,接下来就是并不轻松的布局了。呈现器在创建完成并添加到呈现树时,并不包含位置和大小信息。计算这些值的过程称为布局(Layout)或称为重排(Reflow)。至此Reflow终于现身了。现代浏览器对重排的处理越来越机智,为避免对所有细小更改都进行整体布局,浏览器采用了一种“dirty 位”系统,它会尽可能的利用缓存来减少或用队列分批进行重排。布局分为全局布局(同步)和增量布局(异步),如果更改网页字体大小或是调整窗口大小都会触发全局布局,而类似插入DOM、请求某些样式信息等需要重新计算位置和大小的操作会触发增量布局。
最后进入绘制阶段,系统会遍历呈现树,并调用呈现器的paint方法,将呈现器的内容显示在屏幕上。绘制工作是使用用户界面基础组件完成的。绘制也同样分为全局绘制和增量绘制。系统很巧妙的将多个区域合并成一个。绘制的顺序为:轮廓→子代→边框→背景图片→背景颜色。当有类似颜色改变这样的操作发生后会触发重新绘制(Reprint),它的开销比起重排小很多,在发生变化时,浏览器会尽可能做出最小的响应。
下面是利用Firefox对维基百科页面渲染的可视化视频。
触发重排和重绘的操作
在了解完浏览器渲染页面的原理后,我们很容易总结出几种可以触发重排的操作:
- 修改网页字体大小
- 调整浏览器窗口或是滚动(正常滚动OK,比较有问题的是fixed定位或是有动画的元素)
- 获取元素的位置信息(offset||scroll||clientTop/Left/width/height)
- 对DOM进行增删改移动操作
- 修改CSS的某些样式(尺寸和位置,display:none触发重排,visibility:hidden触发重绘)
利用Chrome观察重排和重绘
Chrome中的DevTools可以很方便的观察重排和重绘。如果你对Chrome DevTools还不太熟悉可以参考Google官方文档和这篇文章进行了解。
最小化重排和重绘
重排和重绘在实际开发中不可避免,我们只能尽量减少重排和重绘的次数,降低浏览器渲染网页的开销,以此带来的性能提升在移动平台上效果显著。
- 不要一条一条的修改CSS属性,最好是整体替换CSS类或重写DOM的cssText属性。
- 将多次DOM修改合并成一次。可以使用documentFragment对象缓存更改,或是复制你需要修改的node节点,修改完成后再替换掉原来的。也可以隐藏元素后再对其进行操作,最后把它显示出来。
- 考虑要修改的元素的层级以及改动它引起的重排面积,选择其中开销最小的方式。
- 不要频繁获取元素的位置属性,如果需要经常使用就用变量把它缓存下来。
- 为需要有动画效果的元素设置position:absolute。同时动画越平滑开销越大,需要在速度和平滑度上取得平滑。
- 保持DOM树正确/简洁,减少不必要的CSS规则和复杂的选择器(尤其是后代选择器)。
- 为页面中的图片显式的声明宽度和高度。
- 不要使用table布局。尽量不要动态更新table元素。
- jQuery中如果为append()方法传入多个元素组成的数组时,jQuery可能会用到documentFragment,但是使用$.each()方法就不会用到documentFragment。
例:
//修改CSS类名而不是逐条修改属性 function changeStyle(element,className) { element.className = className; } //借助DocumentFragment function CreateFragments(){ var fragment = document.createDocumentFragment(); for(var i = 0;i < 10000;i++){ var tmpNode = document.createElement("div"); tmpNode.innerHTML = "test" + i; fragment.appendChild(tmpNode); } document.body.appendChild(fragment); }
原文地址:http://yangjian.me/learning/reflow-and-reprint-in-browsers/
参考资料