# 回流重绘

# 概念

# 回流

所谓回流,就是指“几何属性”需要改变的渲染。

简单点说,触发回流的时候,整个网页都会重新从空白再渲染出来。

回流意味着节点的几何属性改变,需重新计算并生成渲染树,导致渲染树的全部或部分发生变化,相对的它的性能消耗比较大

# 重绘

所谓重绘,就是“几何属性”不受影响,但是“外观属性”会有变化。

所以,回流一定会伴随重绘,重绘却不一定伴随回流。

# 属性分类

那么肯定需要对哪些属性是“几何属性”,哪些是“外观属性”做个分类:

  • 几何属性:包括布局、尺寸等可用数学几何衡量的属性
  • 布局:displayfloatpositionlisttableflexcolumnsgrid
  • 尺寸:marginpaddingborderwidthheight
  • 外观属性:包括界面、文字等可用状态向量描述的属性
  • 界面:appearanceoutlinebackgroundmaskbox-shadowbox-reflectfilteropacityclip
  • 文字:textfontword

其实这个分类没有必要强记,很好理解:这个属性变化的时候,如果对任何一个元素的位置,顺序等有影响,也就是 A 元素变化会导致 B 元素也变化,那就是几何属性,会触发回流。如果 A 只是改个颜色之类的对其他元素位置等完全没有影响,那就是外观属性

# 常见优化

回流重绘在操作节点样式时频繁出现,同时也存在很大程度上的性能问题。PC 端显示的问题可能不会很明显,尤其是现在大家的电脑性能都还不错。但是在移动端可能会造成 加载慢 or 耗电增加 的问题。

一下搜罗了一些常用的减少回流重绘的方法:

# 1. 使用 transform 代替 top

top 是几何属性,操作 top 会改变节点位置从而引发回流,使用 transform:translate3d(x,0,0) 代替 top,只会引发图层重绘,还会间接启动GPU加速。

# 2. 使用 visibility:hidden 替换 display:none【常见考点⭐】

display:none 简称 DNvisibility: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'
1
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>
1
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'
...(省略了许多类似的后续操作)
1
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'
1
2
3
4
5
6
7
8
最后更新: 7/21/2022, 3:30:44 PM