基本概念
DOM Tree
浏览器会将html文本解析成DOM Tree,DOM Tree的每一个节点对应着一个dom元素。DOM Tree中包含了所有的DOM节点,包括display:none
和<head>
。
CSSOM
浏览器会将用户编写的css(包括cdn)与浏览器设置的css解析成一个样式结构体,既CSSOM。
Render Tree
浏览器会使用DOM Tree与CSSOM构建出一个Render Tree,Render Tree的结构与DOM Tree基本相同,只是Render Tree中只包含用于呈现的节点以及对呈现有影响的节点。 所以Render Tree中必然不会包括display:none
的节点和<head>
节点。但是要注意,visibily:hidden
的节点是会被包含在Render Tree中的,虽然这类节点本身不会被呈现出来,但是它们自身是有宽高的,所以是占一定空间的,因此是对呈现有影响的。
Render Tree的构建过程类似于下面这样:
- 从DOM Tree中取出一个节点,判断它对呈现有无影响,如果无影响则忽略。
- 以从右到左的顺序选取此节点上面的选择器名称,去CSSOM里面查找对应的样式。
- 将节点信息和样式结合,生成Render Tree节点。
- 递归遍历整个DOM Tree最终生成Render Tree。
重绘与回流
重绘
用户看到的页面上的这些内容就是根据Render Tree去绘制出来的,所以如果我们修改了某个样式,比如字体颜色或背景颜色,那Render Tree中的节点信息就被我们修改了,此时必然是需要根据Render Tree中的节点信息重新进行绘制的。这便是重绘。
但是需要注意的是,这部分修改的属性是不影响页面布局的,只影响风格。否则就是回流了。
回流
如果我们修改了某个节点的宽高,那么这个节点之后的全部同级的节点,包括其全部子节点的属性都会受到影响,因为它们在页面中的位置发生了变化。此时浏览器就要让RenderTree中所有受影响的节点全部失效,然后重新构建这部分节点。这个过程就叫做回流。回流结束后浏览器还要对所有重新构建的节点进行重绘。
同样的,修改某元素的字体大小,边框宽度,内外边距等都会引起回流,因为这些属性都影响了布局。
区别
回流结束后必定触发重绘,而且回流操作影响的节点非常多,所以回流带来的性能损耗是远远大于重绘的。
何时触发
重绘
- 回流结束后必然触发重绘。
- 对节点的风格做修改的时候会触发重绘。
回流
- 页面首次渲染的时候必然会发生回流,因为这次首次构建Render Tree。
- DOM修改。比如增删元素。
- Render Tree修改。比如改变某节点的宽高,内外边距等。
- js访问或设置元素的布局属性。比如dom.offsetTop、dom.scrollTop、dom.clientTop。
- 浏览器创建resize。比如用户拖拽改变窗口大小。
优化策略
浏览器
说我们自己的优化策略之前,必须说一下浏览器它的优化策略。
浏览器维护了一个操作的缓存队列,当我们引发回流或重构的时候,浏览器不会立刻执行它们,而是将这些操作放到缓存队列中,一旦队列达到一定长度或者距离上次更新过去了足够长的时间,那么浏览器就会一次性执行这个队列中的全部操作(称为flush)。然后统一进行一次回流或重绘。
自己的
布局属性
-
当我们访问或设置offsetTop/Width/Height等属性的时候,浏览器会将一次回流操作加入队列。但如果,在这个队列flush之前我们又访问了同一个布局属性的话,那浏览器为了确保我们拿到的属性是最新的,就会强制进行flush,然后将最新的属性返回给我们。所以如果我们连续频繁的访问布局属性的话,就相当于浏览器的优化策略失效了。
于是,如果我们需要频繁的访问和修改某个布局属性的话,应该把它的值保存到某个变量内,然后对这个变量进行操作,全部操作结束后,再使用这个变量的值去更新相应的布局属性。反例
myElement.style.left = 1 + myElement.offsetLeft + ‘px’
myElement.style.top = 1 + myElement.offsetTop + ‘px’正例
var current = myElement.offsetLeft
myElement.style.left = current + ‘px’
myElement.style.top = current + ‘px’
样式
-
和布局属性一样的道理,如果我们连续修改某个元素的样式的话,那每次修改都会引起回流或重绘。我们应该一次性设置全部的样式。
反例
dom.style.color = “blue”;
dom.style.backgroundColor= “white”;
正例
dom.setAttribute(“style”, “color:blue; background-color: white;”); -
如果某个节点有多种风格的话,我们可以通过切换class来实现,这也是一次修改全部样式的方法。和上面道理一样。
动画
- 做动画应该使用transfrom和opacity,而不是通过连续修改元素的定位实现。因为transfrom只会引起重绘,而修改定位会引起回流。
- 如果需要对一个节点使用动画的话,可以将这个节点脱离文档流,这样回流时候受影响的只是这个节点及其子节点,它对文档流中节点不会产生任何影响。等动画结束再将其加入到文档流中。这样的话,只会引起两次大面积回流操作,一次在移除文档流的时候,一次在加入回文档流的时候,这比起每帧动画都引起大面积回流要强多了。
参考
回流、重绘及其优化 https://segmentfault.com/a/1190000014474575
JavaScript——浏览器的重绘与回流 https://blog.csdn.net/qq_42269433/article/details/81133772
什么是回流,什么是重绘,有什么区别? https://www.jianshu.com/p/e081f9aa03fb