重绘和重排

重排和重绘

重排(也称为回流):当 DOM 的变化影响了元素的几何信息(DOM 对象的位置和尺寸大小,)浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。 触发: 1.添加或者删除可见的 DOM 元素 2.元素尺寸改变——边距、填充、边框、宽度和高度

重绘: 当一个元素的外观,但没有改变布局,重新把元素外观绘制出来的过程,叫重绘。触发: 改变元素的 color、background、box-shadow 等属性

  • 一些不会渲染输出的节点,比如 script、meta、link 等。

  • 一些通过 css 进行隐藏的节点。比如 display:none。注意,利用 visibility 和 opacity 隐藏的节点,还是会显示在渲染树上的。只有 display:none 的节点才不会显示在渲染树上。

注意:渲染树只包含可见的节点

浏览器进行 html 源码解析,并创建一个 dom 树, 每个 html 标签在这个数上都有一个对应的节点,标签中的文本也有一个对应的文本节点。DOM 树上的根节点是 documentElement。

浏览器解析 css 代码,通过一大堆 hack 使她变得有意义, 诸如-moz, -webkit 和其他不能理解的扩展名。

接下来就是创建渲染树(render tree)。渲染树类似 dom 树,但是并不是一一对应的。渲染树基于样式,所以如果你使用 display:none 属性来隐藏一个 div,那么它不会被呈现在渲染树中。其它像 head 标签和所有在其中不可见的元素也一样。另一方面,DOM 元素可能在渲染树中存在多个节点,比如说每行在 中的文本都需要一个渲染节点。渲染树上的节点被称为一个 frame 或者一个 box

var bstyle = document.body.style; // cache
bstyle.padding = "20px"; // 重排+重绘
bstyle.border = "10px solid red"; // 另一次重排+重绘
bstyle.color = "blue"; // 没有尺寸变化,只重绘
bstyle.backgroundColor = "#fad"; // 重绘
bstyle.fontSize = "2em"; // 重排+重绘
// 新的DOM节点 - 重排+重绘
document.body.appendChild(document.createTextNode("dude!"));

浏览器的策略

既然渲染树变化伴随的重排和重绘代价如此巨大,浏览器一直致力于减少这些消极的影响。一个策略就是干脆不做,或者说至少现在不做。浏览器会基于你的脚本要求创建一个变化的队列,然后分批去展现。通过这种方式许多需要一次重排的变化就会整合起来,最终只有一次重排会被计算渲染。浏览器可以向已有的队列中添加变更并在一个特定的时间或达到一个特定数量的变更后执行。

优化重排和重绘

不要逐个变样式。对于静态页面来说,明智且兼具可维护性的做法是改变类名而不是样式。对于动态改变的样式来说,相较每次微小修改都直接触及元素,更好的办法是统一在 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 属性隐藏元素(只有一次重排重绘),添加足够多的变更后,通过 display 属性显示(另一次重排重绘)。通过这种方式即使大量变更也只触发两次重排。 不要频繁计算样式。如果你有一个样式需要计算,只取一次,将它缓存在一个变量中并且在这个变量上工作。看一下下面这个反例:

  1. 页面第一次渲染 在页面发生首次渲染的时候,所有组件都要进行首次布局,这是开销最大的一次重排。

  2. 浏览器窗口尺寸改变

  3. 元素位置和尺寸发生改变的时候

  4. 新增和删除可见元素

  5. 内容发生改变(文字数量或图片大小等等)

  6. 元素字体大小变化

  7. 激活 CSS 伪类(例如::hover)

  8. 设置 style 属性 9. 查询某些属性或调用某些方法。比如说

offsetTop、 offsetLeft、 offsetWidth、 offsetHeight、 scrollTop、 scrollLeft、 scrollWidth、 scrollHeight、 clientTop、 clientLeft、 clientWidth、 clientHeight

除此之外,当我们调用 getComputedStyle 方法,或者 IE 里的 currentStyle 时,也会触发重排,原理是一样的,都为求一个“即时性”和“准确性”。

vidibility、outline、背景色等属性的改变

我们应当注意的是:重绘不一定导致重排,但重排一定会导致重绘。

总结

重新计算渲染树被称为 reflow(在 Firefox 中),在其它浏览器中被成为 relayout

更新屏幕中的显示结果并且重新计算渲染树被成为 repaint 或者在 IE/DynaTrace 中被称为 redraw

两者都是改变页面样式的步骤,

  • 重排步骤包括 Recalculate Style、Layout、Update Layer Tree 等渲染类型事件

  • 重绘步骤包括 Paint 和 Composite Layers 这些绘制类型事件。

什么操作会导致重绘和重排?

  • 添加或删除可见的 DOM 元素

  • 元素的位置发生变化

  • 元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)

  • 内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代。

  • 页面一开始渲染的时候(这肯定避免不了)

  • 浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)

如何减少重绘和重排?

优化点包括重绘和重排的次数和渲染的元素节点数量。

关键在于下面几点:

  • CSS 样式合在一起操作

  • 批量的 DOM 节点脱离文档流再操作

  • 避免多次获取布局信息

  • 使用 CSS3 的 GPU 硬件加速属性 (使用 transform: translateZ(0); 或 transform: translate3d(0, 0, 0); 可以强制浏览器为元素创建一个新的合成层,启用硬件加速) transform opacity filter will-change

  • 延迟渲染

  • 可视区域渲染

transform 会重绘, 回流吗?

不重绘,不回流 是因为 transform 属于合成属性,对合成属性进行 transition/animate 动画时,将会创建一个合成层。这使得动画元素在一个独立的层中进行渲染。当元素的内容没有发生改变,就没有必要进行重绘。浏览器会通过重新复合来创建动画帧。

最后更新于

这有帮助吗?