回流(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)
引发回流的操作:
- 页面首次渲染 2)浏览器窗口大小发生改变 3)元素的尺寸和位置发生改变 4)元素的内容发生改变(文字的数量或字号大小或图片的大小...)
- 增加 删除(可见的Dom)
- 激活css伪类 7)查询某些属性或调用某些方法
- 操作class属性
- 脚本操作DOM
- 计算offsetWidth和offsetHeight属性
- 设置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布局的原因之一。
不要厌烦熟悉的事物,每天都进步一点;不要畏惧陌生的事物,每天都学习一点;