原文连接:http://www.phpied.com/rendering-repaint-reflowrelayout-restyle/javascript
转载连接:http://www.cnblogs.com/ihardcoder/p/3927709.htmlphp
我的备注:css
restyle——没有几何结构改变的渲染树变化(而回流reflow会同时影响布局layout)。html
改变任何影响构造渲染树的行为都会触发重绘repaint,例如java
如何减小重绘repaint和回流reflownode
有没有被标题中的5个“R”吓到?今天,咱们来讨论一下浏览器的渲染(Rendering)-一个产生于Page 2.0生命周期中,甚至有时候会在下载瀑布流中出现的过程。web
咱们来讨论浏览器在接收到HTML、CSS和JavasSript后,如何把你的页面呈如今屏幕上。浏览器
不一样的浏览器的渲染过程存在些许不一样,但大致的机制是同样的,下图展现的是浏览器自下载彻底部的代码后的大体流程缓存
首先咱们先看一个例子:app
<html> <head> <title>Beautiful page</title> </head> <body> <p> Once upon a time there was a looong paragraph... </p> <div style="display: none"> Secret message </div> <div><img src="..." /></div> ... </body> </html>
HTML结构中的每一个标签和标签间的文字都会被映射为DOM树种的一个节点(实际上,空白区域也会被映射为一个text节点,为了简单说明,在此忽略),构建完成的DOM树结构以下:
documentElement (html) head title body p [text node] div [text node] div img ...
因为渲染树会忽略head内容和隐藏的节点,而且会将<p>中的多行文字按行数映射为单独的渲染节点,故构建完成的渲染树结构以下:
root (RenderView) body p line 1 line 2 line 3 ... div img ...
渲染树的根节点是一个包括全部其余节点的结构体(盒子)。你能够将它理解为浏览器窗口的内部区域(我的理解为可绘制区域,即不包括浏览器边框、菜单栏、标签栏等等),页面被限制在此区域内。严格来讲,webkit将渲染树的根节点称为渲染视图-RenderView,渲染视图符合CSS初始包含块-initial containing block,也就是浏览器的整个可绘制区域,从坐标(0,0)到(window.innerWidth,window.innerHeight)。
接下来,咱们将研究浏览器是如何经过循环遍历渲染树把页面展现到屏幕上的。
同一时间内至少存在一个页面初始化layout行为和一个绘制行为(除非你的页面是空白页-blank)。在此以后,改变任何影响构造渲染树的行为都会触发如下一种或者多种动做:
重绘和回流的性能消耗是很是严重的,破坏用户体验,形成UI卡顿。
改变任何影响构造渲染树的行为都会触发重绘,例如
举个栗子:
var bstyle = document.body.style; // 缓存 bstyle.padding = "20px"; // 触发重绘和回流 bstyle.border = "10px solid red"; // 再次触发重绘和回流 bstyle.color = "blue"; // 只触发重绘,由于几何结构没有改变 bstyle.backgroundColor = "#fad"; // 同上 bstyle.fontSize = "2em"; // 再再次触发重绘和回流 // 新增DOM节点,再再再次触发重绘和回流 document.body.appendChild(document.createTextNode('dude!'));
有些回流行为要比其余的花销大一些。设想以下情景,一个直属于body节点的渲染树,若是你在此渲染树中乱搞,它不会影响不少其余节点(这个长句翻译很差,原文以下:Think of the render tree - if you fiddle with a node way down the tree that is a direct descendant of the body, then you're probably not invalidating a lot of other nodes)。可是若是将页面顶部的一个div作动画或改变尺寸,页面的其余部分会被挤来挤去,这听起来会消耗不少性能。
浏览器一直在努力减小消耗巨大的重绘和回流行为。要么选择不执行,要么至少不当即执行。浏览器会生成一个队列用于缓存这些行为而且以块为单位执行它们。经过这种方法,屡次引起重绘或回流的操做会被组合在一块儿,以便在一个回流中完成。浏览器将这些操做加入到缓存队列中,当到达必定的时间间隔,或者累积了足够多的操做行为后执行它们。
可是,有时候某些的代码会破坏上述的浏览器优化机制,致使浏览器刷新缓存队列而且执行全部已已缓存的操做行为。这种状况发生在请求/获取下面这些样式的行为中:
以上的行为本质上是获取一个节点的样式信息,浏览器必须提供最新的值。为了达到此目的,浏览器须要将缓存队列中的全部行为所有执行完毕,而且被强制回流。
因此,在一条逻辑中同时执行set和get样式操做时很是很差的,以下:
el.style.left = el.offsetLeft + 10 + "px";
减小由于重绘和回流引发的糟糕用户体验的本质是尽可能减小重绘和回流,减小样式信息的set行为。能够经过如下几点来优化:
// 糟糕的办法 var left = 10, top = 10; el.style.left = left + "px"; el.style.top = top + "px"; //静态样式经过改变classname // better el.className += " theclassname"; // 动态样式统一修改cssText // better el.style.cssText += "; left: " + left + "px; top: " + top + "px;";
//糟糕的作法 for(big; loop; here) { el.style.left = el.offsetLeft + 10 + "px"; el.style.top = el.offsetTop + 10 + "px"; } //优化后的代码 var left = el.offsetLeft, top = el.offsetTop esty = el.style; for(big; loop; here) { left += 10; top += 10; esty.left = left + "px"; esty.top = top + "px"; }
(废话就不翻译了,大概就是一些吐槽IE开发者工具的话)
如今(原文做于2009年12月)有不少能够帮助咱们深刻了解浏览器重绘和回流机制的工具。
Douglas Crockford去年提到,咱们可能会对一些不太了解的CSS作一些愚蠢的事情,而且我被包括在内。我被引入了一个项目组,研究一种奇怪的现象:在IE6浏览器中增大font-size会引发CPU占用率到达100%,而且会持续10到15分钟,IE浏览器才会完成重绘行为。
有了工具的辅助,咱们没有任何理由再作一些愚蠢的CSS操做了。
顺便提一句,若是有一种像Firebug的工具能够象查看DOM结构同样查看渲染树,是否是很cooooooooooooooool?
下面咱们简单的看一个如何运用工具来证实restyle(没有几何结构改变的渲染树变化)和回流(同时影响布局layout)、重绘。
第一个测试,咱们比较解决同一问题的两种方法。第一种方法,改变一些样式,在每次改变以后检查一次呗改变的样式。
bodystyle.color = 'red'; tmp = computed.backgroundColor; bodystyle.color = 'white'; tmp = computed.backgroundImage; bodystyle.color = 'green'; tmp = computed.backgroundAttachment;
第二种方法,在等待所有样式改变完毕后再检查变化的样式信息。
bodystyle.color = 'yellow'; bodystyle.color = 'pink'; bodystyle.color = 'blue'; tmp = computed.backgroundColor; tmp = computed.backgroundImage; tmp = computed.backgroundAttachment;
上面两种方法用到的几个变量以下:
var bodystyle = document.body.style; var computed; if (document.body.currentStyle) { computed = document.body.currentStyle; } else { computed = document.defaultView.getComputedStyle(document.body, ''); }
上面两中方法的样式改变经过click事件触发。测试页面-restyle.html(点击“dude”)。咱们将第一个测试称为restyle测试。
第二个测试在第一个测试的基础上,同事改变影响布局的样式。
// 每次修改后都检查 bodystyle.color = 'red'; bodystyle.padding = '1px'; tmp = computed.backgroundColor; bodystyle.color = 'white'; bodystyle.padding = '2px'; tmp = computed.backgroundImage; bodystyle.color = 'green'; bodystyle.padding = '3px'; tmp = computed.backgroundAttachment; // 所有修改完毕后再检查 bodystyle.color = 'yellow'; bodystyle.padding = '4px'; bodystyle.color = 'pink'; bodystyle.padding = '5px'; bodystyle.color = 'blue'; bodystyle.padding = '6px'; tmp = computed.backgroundColor; tmp = computed.backgroundImage; tmp = computed.backgroundAttachment;
咱们称第二个测试为relayout测试,测试页面请点击。
咱们经过DynaTrace工具获得restyle测试的表现以下图:
等页面加载完毕后,在第2秒左右点击触发第一种方案(即每次修改样式后当即检查),而后在第4秒左右再次点击触发第二种方案(即等待全部样式修改完毕后再统一检查)。
DynaTrace工具会显示页面的加载过程,从上图能够看到IE的logo图标被加载的时间节点。把鼠标移至Rendering一行以便追踪点击事件,滑动滚轮放大想要追踪的区域能够查看详细信息,以下图:
从上图中能够清晰的看到表明JavaScript行为的蓝色柱形条,一届表明渲染行为的绿色柱形条。经过这个简单的实验,咱们能够注意到两个柱形条的长度,也就是比较渲染行为比JavaScript行为多花费的时间。在Ajax以及富应用中,性能瓶颈并非JavaScript行为,而是DOM节点的操做使用和渲染行为。
接下来咱们来运行relayout测试,也就是涉及几何结构改变的操做行为。经过测试工具的“PurePaths”视图,查看每种行为执行时间的瀑布流。下图中高亮部分显示的是第一次点击事件,执行一段JavaScript逻辑实现一些layout操做。
以下图所示,咱们能够看到在此次的测试中,除了与第一次测试一样的具备表明“绘图”的绿色柱形条之外,还有一个新增的区域-“计算布局流”,由于此次测试中同时触发了重绘和回流。
接下来,咱们经过SpeedTracer工具在Chrome下运行上面两个测试。
第一个测试-restyle测试的运行结果以下图所示:
总的来讲,仍然是一次点击触发一次重绘,可是咱们注意到,在第一次点击的时候,会有50%的时间消耗在计算样式(Style Recalculation)上。致使这种结果的缘由是咱们在每次改变样式后都检查了一次样式信息。
展开事件详细信息后能够清晰的看到,在第一次点击事件后,样式被计算了3次。而第二次点击值计算了一次。以下图所示:
接下来运行第二个测试-relayout测试。整体事件信息与restyle测试大体相同:
可是详情页显示的信息能够看到第一次点击后触发了3次回流(由请求样式信息操做触发),第二次点击只触发了一次回流。经过本工具能够清晰的看到浏览器内部到底发生了什么。
上述两种工具的区别在于:DynaTrace会显示layout行为被执行和加入执行队列的详细时间,而SpeedTracer不会;SpeedTracer会将restyle与reflow/layout两种浏览器行为区别开,而DynaTrace不会。难道IE浏览器自己不会区分这两种行为?另外,在两种不一样的逻辑测试-改变-最后检查(change-end-touch)与改变-当即检查(change-then-touch)中,DynaTrace并不会显示二者触发回流的次数不一样(第一种之触发一次,第二次触发3次,而DynaTrace统一显示为一次),难道IE浏览器的工做机制本就如此?
即便运行上述测试几百次,IE浏览器仍然不关心你在改变样式后是否请求样式信息。(译者注:我彷佛感到原文做者对IE满满的恶意...)
在屡次运行上述测试后,获得几点结论以下:
在全部浏览器(IE系列不在“全部”的范畴)的测试结果显示,只修改样式的时间花销仅仅是同时改变样式和触发layout的一半(我本该对比只改变样式和只改变layout的时间的,可是我没有,不用谢)。顺便提一下IE6,它的layout时间花销是只改变样式的4倍。(呵呵)
很是感谢各位对这篇文章的支持。但愿各位能经过运动上文提到的测试工具改善工做,而且时刻注意回流的触发操做。最后,咱们复习一下几个术语:
扩展阅读,前三篇对浏览器内部机制研究比较深刻,推荐: