回流VS重绘

3,176 阅读6分钟

回流(Reflow)重绘(Repaint)

从最初接触css和html的时候,就听说过回流和重绘,当时只是简单的知道一个是尺寸位置等变化引起,另一个是因为外观样式变化引起,并没有做深入的考究,究竟哪些情况会引起重绘,哪些情况会引起回流,没有做过详细的总结,今天我们就来一起复习总结一下。

一、概念

首先我们来回顾一下浏览器渲染页面的流程 渲染引擎首先通过网络获得所请求文档的内容,通常以8K分块的方式完成。下面是渲染引擎在取得内容之后的基本流程:

  解析html以构建dom树 -> 构建render树 -> 布局render树 -> 绘制render树   

   其次我们来了解一下几个概念,然后引出我们要了解的回流和重绘的概念

1、DOM tree: 浏览器将HTML解析成树形的数据结构((包括display:none的节点))

2、CSS rule Tree: 浏览器将css也解析成树形的数据结构

3、Render Tree: DOM tree和CSSOM tree合并后生成(不包括display:none,head节点,但是包括visibility:hidden的节点)

4、layout: 有了Render Tree之后,浏览器已经知道网页中有哪些节点,各个节点的css定义及他们的从属关系,从而去计算出每个节点在屏幕中的位置。

5、painting: 绘制 按照计算出来的规则,通过显卡,把内容画到屏幕上

reflow(回流) 当浏览器发现某个部分发生了点变化影响了布局,需要倒回去重新渲染,内行称这个回退的过程叫 reflow。reflow 会从 这个 root frame 开始递归往下,依次计算所有的结点几何尺寸和位置。reflow 几乎是无法避免的。现在界面上流行的一些效果,比如树状目录的折叠、展开(实质上是元素的显 示与隐藏)等,都将引起浏览器的 reflow。鼠标滑过、点击……只要这些行为引起了页面上某些元素的占位面积、定位方式、边距等属性的变化,都会引起它内部、周围甚至整个页面的重新渲 染。通常我们都无法预估浏览器到底会 reflow 哪一部分的代码,它们都彼此相互影响着。

repaint(重绘) 改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,但是元素的几何尺寸没有变。

一句话:回流必将引起重绘,重绘不一定会引起回流

二、触发

回流 (Reflow)

引发回流的操作:

  1. 页面首次渲染 2)浏览器窗口大小发生改变 3)元素的尺寸和位置发生改变 4)元素的内容发生改变(文字的数量或字号大小或图片的大小...)
  2. 增加 删除(可见的Dom)
  3. 激活css伪类 7)查询某些属性或调用某些方法
  4. 操作class属性
  5. 脚本操作DOM
  6. 计算offsetWidth和offsetHeight属性
  7. 设置style属性

clientWidth、clientHeight、clientTop、clientLeft offsetWidth、offsetHeight、offsetTop、offsetLeft scrollWidth、scrollHeight、scrollTop、scrollLeft scrollIntoView()、scrollIntoViewIfNeeded() getComputedStyle() getBoundingClientRect() scrollTo()

flush队列 其实浏览器自身是有优化策略的,如果每句 Javascript 都去操作 DOM 使之进行回流重绘的话,浏览器可能就会受不了。所以很多浏览器都会优化这些操作,浏览器会维护 1 个队列,把所有会引起回流、重绘的操作放入这个队列,等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会 flush 队列,进行一个批处理。这样就会让多次的回流、重绘变成一次回流重绘。

但是也有例外,因为有的时候我们需要精确获取某些样式信息,下面这些:

offsetTop, offsetLeft, offsetWidth, offsetHeight scrollTop/Left/Width/Height clientTop/Left/Width/Height width,height 请求了getComputedStyle(), 或者 IE的 currentStyle 这个时候,浏览器为了反馈最精确的信息,需要立即回流重绘一次,确保给到我们的信息是准确的,所以可能导致 flush 队列提前执行了

重绘 (Repaint)

总结

回流比重绘的代价要更高。

三、如何避免回流和重绘

css

1、避免使用table布局。 2、尽可能在DOM树的最末端改变class。 3、避免设置多层内联样式。 4、将动画效果应用到position属性为absolute或fixed的元素上。 5、避免使用CSS表达式(例如:calc())。

javascript

1、避免频繁操作样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性。 2、避免频繁操作DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中。 3、也可以先为元素设置display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。 4、避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。 5、对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。

注意

(1) display:none 的节点不会被加入Render Tree,而visibility: hidden 则会,所以,如果某个节点最开始是不显示的,设为display:none是更优的。

(2) display:none 会触发 reflow,而 visibility:hidden 只会触发 repaint,因为没有发现位置变化。

(3) 有些情况下,比如修改了元素的样式,浏览器并不会立刻reflow 或 repaint 一次,而是会把这样的操作积攒一批,然后做一次 reflow,这又叫异步 reflow 或增量异步 reflow。但是在有些情况下,比如resize 窗口,改变了页面默认的字体等。对于这些操作,浏览器会马上进行 reflow。

(4) 由于浏览器使用流式布局,对Render Tree的计算通常只需要遍历一次就可以完成,但table及其内部元素除外,他们可能需要多次计算,通常要花3倍于同等元素的时间,这也是为什么要避免使用table布局的原因之一。

不要厌烦熟悉的事物,每天都进步一点;不要畏惧陌生的事物,每天都学习一点;