触发reflow(回流)和repaint(重绘)
简单回顾下回流和重绘的定义
- 回流:主要是计算位置和大小
- 重绘:把内容画(更新)到屏幕上
注意:回流一定会触发重绘,而重绘不一定会回流
渲染树发生变化,就会产生回流或重绘,例如:
- DOM节点的增删改
- 隐藏一个DOM节点,用
display:none
(回流和重绘都会触发),visibility: hidden
(只有重绘,因为没有几何变化) - 在页面上移动DOM节点
- 增加或修改样式
- 改变浏览器大小,改变字体大小,甚至滚动页面!
再看一些例子:
var bstyle = document.body.style;
bstyle.padding = "20px"; // 回流,重绘
bstyle.border = "10px solid red"; // 再一次回流和重绘
bstyle.color = "blue"; // 只有重绘,没有颜色变化
bstyle.backgroundColor = "#fad"; // 重绘
bstyle.fontSize = "2em"; // 回流,重绘
// 新加元素 - 回流,重绘
document.body.appendChild(document.createTextNode('dude!'));
复制代码
还有些回流会带来更多的性能损耗,比如你把页面顶部的一个div设置了动画或者拉大了,导致页面下面其他部分都下去了。
浏览器是聪明的
因为渲染树的回流和重绘比较损耗,浏览器目标在于减小负面影响。一个策略就是根本不做这件事情,至少现在不做。浏览器把你写的更改放在一个队列里然后批量执行。用这种方法将会把多次需要回流的更改合成一次回流进行计算。浏览器能够把多次更改放进队列,然后隔一段时间或者达到一定数量时一次性处理掉。
但是有时候,脚本(js)可能会阻止浏览器这项优化措施,导致它(马上)清理队列以及执行所有更改。这件事发生在你修改样式信息,比如:
offsetTop
,offsetLeft
,offsetWidth
,offsetHeight
scrollTop
/Left/Width/HeightclientTop
/Left/Width/HeightgetComputedStyle()
, 或者currentStyle
in IE- 更多
以上这些主要是获取某个节点的样式信息的,一旦你调用这些,浏览器都需要给你最新的值。为了这样做,浏览器就需要执行所有计划中的更改,清理队列执行回流。
举个例子,快速连续地获取和设置样式(循环中),比如:
// no-no!
el.style.left = el.offsetLeft + 10 + "px";
复制代码
减少重绘和回流
几条建议如下:
- 不要一行一行单独地修改样式。最好是直接修改class而不是修改样式,这样也比较可维护,不够这个只对于静态样式而言。如果样式是动态,那最好是用
cssText
去做。
// bad
var left = 10,
top = 10;
el.style.left = left + "px";
el.style.top = top + "px";
// better
el.className += " theclassname";
// 或者需要动态的修改top和left...
// better
el.style.cssText += "; left: " + left + "px; top: " + top + "px;";
复制代码
-
以“离线”方式批量处理DOM更改。离线意思是不在真实的DOM树下(做更改),你可以:
- 使用
documentFragment
去临时存一下更改 - 克隆一个节点出来,然后在这个克隆的节点上做修改,然后在和原来的节点做替换
- 先用
display:none
(需要一次回流及重绘)隐藏掉元素,增加100次修改,然后再显示(再花费一次回流重绘)。这样你就拿2次回流和重绘换掉了100次。
- 使用
-
不要过分地使用computed styles。如果你需要使用,那就拿一次,然后保存到本地变量里,并对这个本地变量进行操作。重新看下那个no-no例子:
// no-no!
for(big; loop; here) {
el.style.left = el.offsetLeft + 10 + "px";
el.style.top = el.offsetTop + 10 + "px";
}
// better
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";
}
复制代码
- 大体上,在你做了更改之后,需要考虑一下渲染树里有多少(节点)需要重新验证。比如,使用绝对定位(absolute positioning)把一个节点作为渲染树中的一个主体节点,那么给这个节点设置动画的时候,不会影响太多别的节点,一些在(动画涉及到的)区域内节点可能需要重绘,但是它们不需要回流。