综述
- 回流性能消耗大,尽量避免回流
- 不要频繁获取布局信息,对频繁变化元素采用absolute/fixed/硬件加速(opacity、transform动画、3D属性)
- 硬件加速时要注意复合层会将其父元素和前置高Index兄弟元素都变成复合层,过多复合层也会影响性能
- 使用transform代替left、top减少使用引起页面重排的属性
回流和重绘
-
回流(也叫重排:Reflow,对应Performance的Rendering)
当页面中的html布局结构发生改变(增加、删除、移动节点、改变节点大小),浏览器都需要重新计算新的DOM结构,重新对当前的页面进行渲染,回流一定会触发重绘。
回流 -
重绘 ( Repaint 对应Performance的Painting)
当某个元素的部分样式(如背景颜色)发生改变但不影响它周围或内部布局的属性时,浏览器需要重新渲染当前元素即可。-
display:none会触发 reflow,而visibility:hidden只会触发 repaint,因为没有发生位置变化。 -
requestAnimationFrame传入的回调方法会在每次重绘之前被执行
重绘
-
-
合成
CPU完成回流/重绘后会将图层交给GPU进行合成。
如果更改一个既不要布局也不要绘制的属性(如transform),则直接由GPU进行合成运算,性能是最好的。- 因此CSS
transform动画比JS操纵width/height/top/left等性能更好 -
合成流程不在主线程执行,因此不会和JS或其他布局任务相互阻塞。
合成
- 因此CSS
通常有如下行为可以触发回流:
- 页面初次渲染
- 添加或删除可见的DOM元素
- 元素的位置或尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)
- 激活 CSS 伪类(例如:hover)
- 内容发生变化,比如文本/字体变化或图片被另一个不同尺寸的图片所替代。
- 浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)
- 获取布局信息
浏览器正常情况下会将dom的修改加入一个队列,等达到阈值或经过一段时间一起回流。但当JS试图获取布局信息时会强制进行一次layout,以保证获取的信息是dom修改之后的。因此以下属性应避免频繁调用,且不要和修改dom穿插进行:- offsetTop、offsetLeft、offsetWidth、offsetHeight
- scrollTop、scrollLeft、scrollWidth、scrollHeight
- clientTop、clientLeft、clientWidth、clientHeight
- getComputedStyle()
- getBoundingClientRect
- scrollTo()
避免回流的方法
- 不要频繁调用布局信息,且不要和修改dom穿插进行
- 对动画元素使用定位为
absolute或fxed脱离文档流,则其改变时无需回流其他元素 - 避免使用
calc()表达式 - 避免使用
table布局,一处改动会造成整个 table 回流 - 尽可能在 DOM 树的最子级操作dom,引起的回流较小
- 启用复合图层(CSS3硬件加速、GPU加速)
- 合并修改或离线后修改(因现代浏览器已采用队列模式,无需手动优化)
- 把 DOM 离线后修改,如
display:none、documentFragment、克隆dom对象等方式 - 使用
cssText或class代替一次性修改多个样式
- 把 DOM 离线后修改,如
const el = document.getElementById('test');
el.style.cssText += 'border-left: 1px; border-right: 2px; padding: 5px;';
复合图层(GPU加速)
复合图层会触发硬件加速,通过浏览器的GPU进程,使用GPU而非CPU进行渲染
默认情况下所有dom虽然有层叠上下文的区别,但依然处于同一图层(absolute、fixed也在默认图层)
复合图层repaint时,只repaint本身,不会影响到其他的层
复合图层transform和opacity变化,只触发合成,不会触发回流和重绘
复合图层会占用额外资源,因此复合图层过多也会影响性能。
通过Chrome开发者工具=>more tools=>layers可以查看图层
- 通常有如下方法创建复合图层:
-
will-change: left, top;(默认值为auto) 传入即将变化的值,让浏览器实现做好准备。
注意:过度使用会导致更严重的性能问题。 - 启用3D效果,如
translate3d、translateZ、rotateZ、backface-visibility:hidden
一个常见的启用复合图层的手段:translateZ(0) - 涉及
opacity/transform的 animation 或者 transition(只有动画执行的过程中才会创建合成层,动画开始前或结束后元素还会回到之前的状态) -
<video>、<webgl>、带有复合图层内容的<iframe>、 3D或硬件加速的2D<canvas>等元素 - 元素有包含复合层的后代节点,且元素本身具有transform、opactiy(小于 1)、mask、fliter、reflection、overflow(不为visible)、position:fixed、
-
元素有一个z-index较低且包含一个复合层的前置兄弟元素(换句话说就是该元素在复合层之上渲染)
元素与复合图层元素发生重叠,则元素也形成复合图层。
如元素本身存在属性overflow:auto或overflow:scroll,则其所有子元素也形成复合图层。 - 其它,譬如以前的flash插件
-
硬件加速时请使用较大的z-index
如果这个元素添加了硬件加速,并且z-index层级比较低 (或为static,即没有z-index层级),那么在这个元素的后面其它元素(z-index不小于该元素且为relative或absolute定位)按照上述创建方式6,会默认变为复合层渲染,如果处理不当会极大的影响性能
-
通过以下方法解决该问题
- 将该元素z-index变得比后方其他元素大
- 将该元素移动到文档最后
- 给后方元素一个更小的z-index
在这篇文章中的demo可以看出z-index的必要。
页面中设置了一个h1标题,应用了translate3d动画,使得它被放到composited layer中渲染,然后在这个元素后面创建了2000个list。在h1元素未设置z-index的情况下,本不需要提升到合成层的ul元素下的每个li元素都提升为一个单独合成层(每个li元素的黄色提示边框),最终会导致GPU资源过度消耗页面滑动时很卡,尤其在移动端(安卓)上更加明显。
选中"为动画元素设置z-index",可以看出ul下的每个li都回归到普通渲染层,不再是合成层也就不会消耗GPU资源去渲染,从而达到了优化页面性能优化的目的。



