回流

回流又名重排,指 布局 或 几何属性需改变的渲染。
渲染树的节点发生改变,影响了该节点的几何属性,导致该节点位置发生变化,此时就会触发浏览器回流并重新生成渲染树。
回流意味着节点的几何属性改变,需重新计算并生成渲染树,导致渲染树的全部或部分发生变化。

重绘

重绘是指当节点需要更改外观而不会影响布局的,比如改变 color 就叫称为重绘。
渲染树的节点发生改变,但不影响该节点的几何属性。
由此可见,回流对浏览器性能的消耗是高于重绘的,而且回流一定会伴随重绘,重绘却不一定伴随回流。

属性分类

查询属性渲染状态的网站 CssTriggers

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

image.png

性能优化

回流重绘在操作节点样式时频繁出现,同时也存在很大程度上的性能问题。
回流必定引发重绘,重绘不一定引发回流,可利用该法则解决一些因为回流重绘而引发的性能问题。
会触发回流的情况:

  • 改变窗口大小
  • 修改盒模型
  • 增删样式
  • 重构布局
  • 重设尺寸
  • 改变字体
  • 改动文字

使用visibility:hidden替换display:none

笔者从以下四方面对比display:nonevisibility:hiddendisplay:none简称DNvisibility:hidden简称VH

  • 占位表现
    • DN不占据空间
    • VH占据空间
  • 触发影响
    • DN触发回流重绘
    • VH触发重绘
  • 过渡影响
    • DN影响过渡不影响动画
    • VH不影响过渡不影响动画
  • 株连效果
    • DN后自身及其子节点全都不可见
    • VH后自身及其子节点全都不可见但可声明子节点visibility:visible单独显示

两者的占位表现触发影响株连效果就能说明VH代替DN的好处,从两者区别中就能找出恰当的答案了。

使用transform代替top

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

避免使用Table布局

牵一发而动全身用在Table布局身上就很适合了,可能很小的一个改动就会造成整个<table>回流,有兴趣的同学可用Chrome DevtoolsPerformance调试看看,在此就不演示了。
通常可用<ul><li><span>等标签取代<table>系列标签生成表格。

避免规则层级过多

浏览器的CSS解析器解析css文件时,对CSS规则是从右到左匹配查找,样式层级过多会影响回流重绘效率,建议保持CSS规则在3层左右。

避免节点属性值放在循环里当成循环变量

JS 中 获取几何属性时也会触发回流 ( 获取属性值时,会重新计算获取最新的值,导致回流 )
现代的浏览器都是很聪明的,由于每次重排都会造成额外的计算消耗,因此大多数浏览器都会通过队列化修改并批量执行来优化重排过程。浏览器会将修改操作放入到队列里,直到过了一段时间或者操作达到了一个阈值,才清空队列。但是!当你获取布局信息的操作的时候,会强制队列刷新,比如当你访问以下属性或者使用以下方法:

  • offsetTop、offsetLeft、offsetWidth、offsetHeight
  • scrollTop、scrollLeft、scrollWidth、scrollHeight
  • clientTop、clientLeft、clientWidth、clientHeight
  • getComputedStyle()
  • getBoundingClientRect
  • 具体可以访问这个网站:https://gist.github.com/pauli…点击预览

以上属性和方法都需要返回最新的布局信息,因此浏览器不得不清空队列,触发回流重绘来返回正确的值。因此,我们在修改样式的时候,最好避免使用上面列出的属性,他们都会刷新渲染队列。如果要使用它们,最好将值缓存起来。

动态改变类而不改变样式 ( 最小化重绘和重排 )

不要尝试每次操作DOM去改变节点样式,这样会频繁触发回流。
更好的方式是使用新的类名预定义节点样式,在执行逻辑操作时收集并确认最终更换的类名集合,在适合时机一次性动态替换原来的类名集合。

  • 使用cssText

    1. const el = document.getElementById('test');
    2. el.style.cssText += 'border-left: 1px; border-right: 2px; padding: 5px;';
  • 修改CSS的class

    1. const el = document.getElementById('test');
    2. el.className += ' active';