back back-top comments magnifier menu mobile right smile views

深入探讨 repaint 和 reflow

Jean
Jean

在了解 repaint 和 reflow 之前,先来瞧瞧浏览器引擎是如何解析和渲染网页的,贴张图:

browser-parse-step

浏览器引擎解析和渲染网页的步骤:

Step 1:浏览器引擎首先开始解析 HTML 文档,将各标签逐个转化成 DOM 节点,即 DOM 树

Step 2:在解析 HTML 文档的同时也会解析外部 CSS 文件以及 HTML 中 Style 标签里的样式数据(也包括 JS 动态生成的样式),这些样式数据将用于创建另一个树结构:渲染树。渲染树包含多个带有视觉属性(如颜色和尺寸)的矩形。这些矩形的排列顺序将决定它们在屏幕上显示的顺序。

Step 3:渲染构建完毕之后,进入 布局处理阶段,也就是为每个节点分配一个应该出现在屏幕上的确切坐标。

Step 4:下面是 绘制阶段,浏览器渲染引擎会遍历渲染树,由用户界面的后端层将每个节点绘制出来。

( 整个过程是渐进完成的,渲染引擎为了尽快将内容显示在屏幕上,不等整个 HTML 文档解析完毕,就开始构建渲染树和设置布局,部分内容开始解析并显示出来,直到整个网页呈现在我们面前。)

我们所关心的 repaintreflow 就发生在 step 3 和 step 4。

什么是 repaint 和 reflow ?

repaint —— 重绘,当一个 DOM 元素的外观发生改变时(例如:outline, visibility, color, background color 等改变), 浏览器会根据元素的新样式属性重新绘制,使元素呈现新的外观,这个过程叫做 重绘,它不会影响到布局。重绘的性能代价是高昂的,因为浏览器必须验证 DOM 树上其他节点元素的可见性。

reflow —— 回流,可以理解为渲染树需要重新计算,并根据计算的结果 将元素渲染到它所在屏幕上的确切坐标,回流影响到部分(或整个)页面的布局,一个元素回流会导致它所有的子元素、祖先元素、兄弟元素发生回流。

例如下面代码中,当 div.element 发生回流 会导致它的祖先元素(body, div.wrap)、子元素(span)以及兄弟元素(p)发生回流

<body>
  <div class="wrap">
    <div class="element">
      <span>child</span>
    </div>
    <p>sibling</p>
  </div>
</body>

回流又会导致重绘,所以它的性能代价是更加昂贵的!repaint 和 reflow 是影响 JavaScript 性能的主要原因之一。它们如果发生在移动端影响会比 PC 端大的多 并导致耗电。

什么情况只发生 repaint(重绘)

  • 仅修改 DOM 元素的 颜色属性(color, background-color, border-color …)或 visibility、outline 属性,因为不会改变布局 所以只发生 repaint

哪些情况会发生 reflow(回流)

  • 1. 使用JS 增加、删除、或修改 DOM 节点(包括移除或增加样式表)
  • 2. 设置元素的内联样式 即 style 属性
  • 3. 修改元素 class 属性
  • 4. 更改字体
  • 5. CSS 中修改 display 属性
  • 6. 网页上内容发生变化,例如在 input 或 textarea 里输入文字
  • 7. 计算 offsetWidth 和 offsetHeight 属性
  • 8. 缩放(Resize)或滚动浏览器窗口(移动端不存在缩放浏览器窗口的情况)
  • 9. 另外,在 webkit 中,当元素应用了 position 为 fixed 或 absoult 时,修改它们的其他 CSS 样式 也只会发生回流(webkit 会作为 layout 操作,其他浏览器只发生重绘)

看来,回流是很容易发生的,如何优化呢?

一些有关减少或避免 reflow(回流)的建议

操作元素样式的技巧:

1. 避免同时设置多个元素的内联样式(style 属性)

2. 实现动画时,最好应用在 position 为 fixed 或 absolute 的元素上

3. 避免使用 table 布局

4. 不要使用 CSS JavaScript 表达式(例如:color: expression((new Date()).getHours()%2 ? “#0091ea” : “#00b8d4” )

修改 DOM 的技巧:

1. 如果想用 JS 修改元素的样式,最好通过改变元素的 class 名,并尽可能在 DOM 树最末端的节点上修改(例如可以想办法只修改元素子节点上的 class)

2. 不要多次修改 DOM,可以使用 document.createDocumentFragment() 把要改的 DOM 节点缓存起来 在内部修改,再一次性添加进 HTML 中

3. 将要修改的 DOM 节点设置 display:none,会有一次 repaint,接着可以多次修改,修改完后再设置为 display:block

浏览器一直在努力中

为了避免和减少回流对性能的伤害,浏览器厂商一直在升级,比如现代浏览器已经对连续修改内联样式(style)的行为进行优化,将多次 reflow 合为一次。

本文由 前端先生 原创,欢迎转载分享,但请注明出处。

0条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注

扫描二维码分享到微信