网页重绘和回流以及解决方法
浏览器呈现网页的处理流程图:
具体处理步骤:
1.浏览器把获取到的HTML代码解析成一棵DOM树,HTML中的每个标签(tag)都是DOM树中的一个节点,根节点就是我们常用的document对象。DOM树里包含了HTML所有标签,包括display:none隐藏,还有用JS动态添加的元素等;
2.浏览器把所有样式(用户定义的css和用户代理)解析成样式结构体,在解析过程中会去掉浏览器不能识别的样式,比如IE会去掉-moz开头的样式,而Firefox会去掉_开头的样式;
3.DOM树和样式结构体组合后构建render tree(渲染树),render tree类似于DOM树,但区别很大,render tree 能识别样式,render tree的每一个节点都有自己的样式,而且render tree中不包含隐藏的节点(比如display:none的节点,还有head节点),因为这些节点不会用于呈现,而且不会影响呈现。
注意:visibility:hidden隐藏的元素还是会包含到render tree中,因为visibility:hidden会影响布局(layout),会占有空间。根据css2的标准,render tree中的每个节点都称为Box(Box demensions),理解页面元素为一个具有填充,边距,边框和位置的盒子。
4.一旦render tree构建完成后,浏览器就可以根据render tree来绘制页面了。
重绘
重绘是一个元素外观的改变所触发的浏览器行为,例如改变visibility,outline,背景色属性。浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。
回流
回流是更明显的一种改变,可以理解为render tree需要重新计算。每个页面至少需要一次回流,就是在页面加载的时候。
注意:回流必将引起重绘,但重绘不一定引起回流。
触发回流
1.调整窗口大小(Resizing the windown);
2.改变字体(changing the font);
3.增加或移除样式表(adding or removing a stylesheet);
4.内容变化,比如用户在input输入框中输入文字(content changes ,such as a user typing text in an input box);
5.激活css伪类,比如:hover(IE中为兄弟节点伪类的激活)(activation of css pseudo classes such as :hover (in IE the activation of the pseduo class of a silibing));
6.操作class属性(manipulating the class attribute);
7.脚本操作DOM(a script manipulating the DOM);
8.计算offsetWidth和offsetHeight属性(calculating offsetWidth and offsetHeight);
9.设置style属性的值(setting a property of the style attribute)。
如何减少回流和重绘
1.直接改变className,如果动态改变样式,则使用cssText(考虑没有优化的浏览器);
// 不好的写法 var left = 1; var top = 1; el.style.left = left + "px"; el.style.top = top + "px";// 比较好的写法 el.className += " className1"; // 比较好的写法 el.style.cssText += "; left: " + left + "px; top: " + top + "px;";
2.让操作的元素进行“离线处理”,处理完后一次更新;
使用DocumentFragment进行缓存操作,引发一次回流和重绘;
使用display:none属性,只引发两次回流和重绘;
使用cloneNode(true or false)和replaceChild技术,引发一次回流和重绘。
3.不要经常访问会引起浏览器flush队列的属性,如果你确定要缓存,利用缓存;
// 不好的写法 for(循环) { el.style.left = el.offsetLeft + 5 + "px"; el.style.top = el.offsetTop + 5 + "px"; } // 这样写好点 var left = el.offsetLeft, top = el.offsetTop, s = el.style; for (循环) { left += 10; top += 10; s.left = left + "px"; s.top = top + "px"; }
4.让元素脱离动画流,减少回流的render tree的规模;
$("#block1").animate({left:50}); $("#block2").animate({marginLeft:50});
5.尽可能在DOM树的最末端改变class(可限制回流的范围);
6.避免设置多层内联样式(将样式合并在一个外部类,仅产生一次回流);
7.动画效果应用到position属性为absolute或fixed的元素上(动画效果应用到position属性为absolute或fixed的元素上,它们不影响其他元素的布局,所它他们只会导致重新绘制,而不是一个完整回流。这样消耗会更低。);
8.牺牲平滑度换取速度(Opera还建议我们牺牲平滑度换取速度,其意思是指您可能想每次1像素移动一个动画,但是如果此动画及随后的回流使用了 100%的CPU,动画就会看上去是跳动的,因为浏览器正在与更新回流做斗争。动画元素每次移动3像素可能在非常快的机器上看起来平滑度低了,但它不会导 致CPU在较慢的机器和移动设备中抖动。);
9.避免使用table数据(在布局完全建立之前,table经常需要多个关口,因为table是个和罕见的可以影响在它们之前已经进入的DOM元素的显示的元素。);
10.避免使用css的JavaScript表达式(因为他们每次重新计算文档,或部分文档、回流。正如我们从所有的很多事情看到的:引发回流,它可以每秒产生成千上万次。)。
实例测试一:
测试代码不改变元素的规则,大小,位置。只改变颜色,所以不存在回流,仅测试重绘,代码如下:
<body> <script type="text/javascript"> var s = document.body.style; var computed; if (document.body.currentStyle) { computed = document.body.currentStyle; } else { computed = document.defaultView.getComputedStyle(document.body, ''); } function testOneByOne(){ s.color = 'red';; tmp = computed.backgroundColor; s.color = 'white'; tmp = computed.backgroundImage; s.color = 'green'; tmp = computed.backgroundAttachment; } function testAll() { s.color = 'yellow'; s.color = 'pink'; s.color = 'blue'; tmp = computed.backgroundColor; tmp = computed.backgroundImage; tmp = computed.backgroundAttachment; } </script> color test <br /> <button onclick="testOneByOne()">Test One by One</button> <button onclick="testAll()">Test All</button> </body>
实例测试二:
跟第一次测试的代码很类似,但加上了对layout的改变,为的是测试回流。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1 /DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> </head> <body> <script type="text/javascript"> var s = document.body.style; var computed; if (document.body.currentStyle) { computed = document.body.currentStyle; } else { computed = document.defaultView.getComputedStyle(document.body, ''); } function testOneByOne(){ s.color = 'red'; s.padding = '1px'; tmp = computed.backgroundColor; s.color = 'white'; s.padding = '2px'; tmp = computed.backgroundImage; s.color = 'green'; s.padding = '3px'; tmp = computed.backgroundAttachment; } function testAll() { s.color = 'yellow'; s.padding = '4px'; s.color = 'pink'; s.padding = '5px'; s.color = 'blue'; s.padding = '6px'; tmp = computed.backgroundColor; tmp = computed.backgroundImage; tmp = computed.backgroundAttachment; } </script> color test <br /> <button onclick="testOneByOne()">Test One by One</button> <button onclick="testAll()">Test All</button> </body> </html>
避免回流和重绘的次数可从代码的角度来实现性能优化,因此我们在写代码时应尽量使用上诉方法来减少网页回流和重绘的次数。