【译】重绘与回流no-no篇

1,258 阅读3分钟

触发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)可能会阻止浏览器这项优化措施,导致它(马上)清理队列以及执行所有更改。这件事发生在你修改样式信息,比如:

  1. offsetTop, offsetLeft, offsetWidth, offsetHeight
  2. scrollTop/Left/Width/Height
  3. clientTop/Left/Width/Height
  4. getComputedStyle(), 或者 currentStyle in IE
  5. 更多

以上这些主要是获取某个节点的样式信息的,一旦你调用这些,浏览器都需要给你最新的值。为了这样做,浏览器就需要执行所有计划中的更改,清理队列执行回流。

举个例子,快速连续地获取和设置样式(循环中),比如:

// 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)把一个节点作为渲染树中的一个主体节点,那么给这个节点设置动画的时候,不会影响太多别的节点,一些在(动画涉及到的)区域内节点可能需要重绘,但是它们不需要回流。

原文