# 回流重绘
# 概念
# 回流
所谓回流,就是指“几何属性”需要改变的渲染。
简单点说,触发回流的时候,整个网页都会重新从空白再渲染出来。
回流意味着节点的几何属性改变,需重新计算并生成渲染树,导致渲染树的全部或部分发生变化,相对的它的性能消耗比较大。
# 重绘
所谓重绘,就是“几何属性”不受影响,但是“外观属性”会有变化。
所以,回流一定会伴随重绘,重绘却不一定伴随回流。
# 属性分类
那么肯定需要对哪些属性是“几何属性”,哪些是“外观属性”做个分类:
- 几何属性:包括布局、尺寸等可用数学几何衡量的属性
- 布局:
display
、float
、position
、list
、table
、flex
、columns
、grid
- 尺寸:
margin
、padding
、border
、width
、height
- 外观属性:包括界面、文字等可用状态向量描述的属性
- 界面:
appearance
、outline
、background
、mask
、box-shadow
、box-reflect
、filter
、opacity
、clip
- 文字:
text
、font
、word
其实这个分类没有必要强记,很好理解:这个属性变化的时候,如果对任何一个元素的位置,顺序等有影响,也就是 A 元素变化会导致 B 元素也变化,那就是几何属性,会触发回流。如果 A 只是改个颜色之类的对其他元素位置等完全没有影响,那就是外观属性。
# 常见优化
回流重绘在操作节点样式时频繁出现,同时也存在很大程度上的性能问题。PC 端显示的问题可能不会很明显,尤其是现在大家的电脑性能都还不错。但是在移动端可能会造成 加载慢 or 耗电增加 的问题。
一下搜罗了一些常用的减少回流重绘的方法:
# 1. 使用 transform 代替 top
top
是几何属性,操作 top
会改变节点位置从而引发回流,使用 transform:translate3d(x,0,0)
代替 top
,只会引发图层重绘,还会间接启动GPU加速。
# 2. 使用 visibility:hidden 替换 display:none【常见考点⭐】
display:none
简称 DN
,visibility:hidden
简称 VH
。
- 占位表现
- DN不占据空间
- VH占据空间
- 触发影响
- DN触发回流重绘
- VH触发重绘
- 过渡影响
- DN影响过渡不影响动画
- VH不影响过渡不影响动画
- 株连效果
- DN后自身及其子节点全都不可见
- VH后自身及其子节点全都不可见但可声明子节点
visibility:visible
单独显示
# 3. 尽量避免使用 table 布局
table 内一些很小的改动会引起整个 table 的回流。
# 4. 避免样式节点层级过多
浏览器的 CSS 解析器在解析 css 文件时,对 CSS 规则是从右到左匹配查找,样式层级过多会影响回流重绘效率,建议保持CSS规则在 3层 左右。
# 5. 当心操作 DOM
js 去直接操作 DOM 很容易频繁的触发回流,尽管现代浏览器已经很“聪明”地会优化一些操作,但是我们在进行处理的时候也要当心:
# 动态改变类名而不改变样式
举个例子
const container = document.getElementById('container')
container.style.width = '100px'
container.style.height = '200px'
container.style.border = '10px solid red'
container.style.color = 'red'
2
3
4
5
改变宽、高、边框很容易触发多次回流,尽管现代浏览器有Flush 队列,会把我们触发的回流与重绘任务都塞进去,待到队列里的任务多起来、或者达到了一定的时间间隔,或者“不得已”的时候,再将这些任务一口气出队。
但是我们建议的做法还是将它们统一放到一个 class
里,利用 classList
(opens new window) 来进行处理:
<style>
.basic_style {
width: 100px;
height: 200px;
border: 10px solid red;
color: red;
}
</style>
<script>
const container = document.getElementById('container')
container.classList.add('basic_style')
</script>
2
3
4
5
6
7
8
9
10
11
12
# 将 DOM “离线”
我们上文所说的回流和重绘,都是在“该元素位于页面上”的前提下会发生的。一旦我们给元素设置 display: none
,将其从页面上“拿掉”,那么我们的后续操作,将无法触发回流与重绘——这个将元素“拿掉”的操作,就叫做 DOM 离线化。
对比如下代码:
const container = document.getElementById('container')
container.style.width = '100px'
container.style.height = '200px'
container.style.border = '10px solid red'
container.style.color = 'red'
...(省略了许多类似的后续操作)
2
3
4
5
6
离线化后:
let container = document.getElementById('container')
container.style.display = 'none'
container.style.width = '100px'
container.style.height = '200px'
container.style.border = '10px solid red'
container.style.color = 'red'
...(省略了许多类似的后续操作)
container.style.display = 'block'
2
3
4
5
6
7
8