我们首先来回顾一下渲染流水线的流程:
接下来,我们将来以此为依据来介绍重绘和回流,以及让更新视图的另外一种方式――合成。
1.了解浏览器的渲染机制
- 浏览器采用流式布局模型。
- 首先浏览器会将HTML解析成DOM,把CSS解析成CSSOM,把CSSOM与DOM结合产生render tree。
- 有render tree之后,我们知道了节点样式,然后浏览器会计算节点的位置,然后把节点绘制到页面上。
总结:回流一定会引起重绘,重绘不一定会引起回流。
2.回流(重排)
首先介绍回流。回流也叫重排。
当我们render tree中的一些元素的结构或者尺寸等发生改变,浏览器重新渲染部分或者全部文档的过程就叫做回流。
(1)触发条件
一般来说,就是当我们对DOM结构的修改引发DOM几何尺寸变化的时候,会发生回流的过程。具体一点,有以下的操作会触发回流:
- 一个DOM元素的几何属性变化,常见的几何属性有width、height、padding 、margin 、1eft、top、border等等,这个很好理解。
- 使DOM节点发生增减或者移动。
- 读写offset族、scroll族和client族属性的时候,浏览器为了获取这些值,需要进行回流操作。
- 调用window.getcomputedstyle方法。
简单一些,就是有以下操作会导致回流:
- 页面首页渲染
- 浏览器窗口大小发生变化
- 内容变换
- 添加或者删除节点
- 激活CSS伪类
(2)回流过程
依照上面的渲染流水线,触发回流的时候,如果DOM结构发生改变,则重新渲染DOM树,然后将后面的流程(包括主线程之外的任务)全部走—遍。
相当于将解析和合成的过程重新又走了一篇,开销是非常大的。
3.重绘
(1) 触发条件
当DOM的修改导致了样式的变化,并且没有影响几何属性的时候,会导致重绘( repaint)。
一般来说,导致重绘的操作有:
当页面中元素样式的改变不影响它在文档流中的位置,浏览器会将新样式赋予给元素,这个过程叫做重绘,或者是一个元素的外观被改变,(背景色,字体颜色,边框颜色等)
(2)重绘过程
由于没有导致DOM几何属性的变化,因此元素的位置信息不需要更新,从而省去布局的过程。流程如下:
跳过了生成布局树和建图层树的阶段,直接生成绘制列表,然后继续进行分块、生成位图等后面一系列操作。
优化思路:
- 能重绘尽量不重排
- 能少排不多排
- 能小区域不大区域排
- 避免无效重排
- 利用GPU资源
合成
还有一种情况,是直接合成。比如利用CSS3的transform、opacity、filter这些属性就可以实现合成的效果,也就是大家常说的GPU加速。
(1)GPU加速的原因
在合成的情况下,会直接跳过布局和绘制流程,直接进入非主线程处理的部分,即直接交给合成线程处理。交给它处理有两大好处:
- 能够充分发挥GPU的优势。合成线程生成位图的过程中会调用线程池,并在其中使用GPu进行加速生成,而GPU是擅长处理位图数据的。
- 没有占用主线程的资源,即使主线程卡住了,效果依然能够流畅地展示。
作为一名前端开发人员,需要对浏览器的开销情况也有所了解,重绘和重排都会开销,以下几种方法可以减少开销
(1)分离读写操作
div.style.left ='10px';
div.style.top = '10px ';
div.style.width ='20px';
div.style.height ='20px';
console.log(div.offsetLeft);
console.log(div.offsetTop);
console.log(div.offsetwidth);
console.log(div.offsetHeight);
由于属性读取会强制重排后在读取,所以尽量避免读一个写一个属性读一个属性。
(2) 样式集中改变
// bad
var left = 10;
var top = 10;
el.style.left = left + "px";
el.style.top = top+ "px" ;
//good
el.className += " theclassname" ;
// good
el.style.cssText += " ; left: " + left + "px; top: " + top + "px; " ;
(3) 缓存布局信息
// bad强制刷新 触发两次重排
div.style.left = div.offsetLeft + 1 + 'px ';
div. style.top = div.offsetTop + 1 + 'px ' ;
// good缓存布局信息 相当于读写分离
var curLeft = div.offsetLeft;
var curTop = div.offsetTop;
div.style.left = curLeft + 1 + 'px';
div.style.top = curTop + 1 + 'px';
(4) 离线改变dom
隐藏要操作的dom
(1) 在要操作dom之前,通过display隐藏dom,当操作完成之后,才将元素的display属性为可见,因为不可见的元素不会触发重排和重绘。
dom.display = 'none'
//修改dom样式
dom .display ='block'
(2) 通过使用DocumentFragment创建一个dom碎片,在它上面批量操作dom,操作完成之后,再添加到文档中,这样只会触发一次重排。
(3) 复制节点,在副本上工作,然后替换它
(5) position属性为absolute或fixed
position属性为absolute或fixed的元素,重排开销比较小,不用考虑它对其他元素的影响
(6) 优化动画
- 可以把动画效果应用到position属性为absolute或fixed的元素上,这样对其他元素影响较小
- 动画效果还应牺牲一些平滑,来换取速度
- GPU硬件加速是指应用GPU的图形性能对浏览器中的一些图形操作交给GPU来完成,因为GPU是专门为处理图形而设计,所以它在速度和能耗上更有效率。GPU加速通常包括以下几个部分:Canvas2D,布局合成,CSS3转换(transitjons) , CSS3 3D变换(transforms) , WebGL和视频(video)。-
实际意义
-
知道上面的原理之后,对于开发过程有什么指导意义呢?
-
避免频繁使用style,而是采用修改class的方式。
-
使用createDocumentFragment进行批量的DOM操作。
-
对于resize、scroll等进行防抖/节流处理。
添加will-change: tranform,让渲染引擎为其单独实现一个图层,当这些变换发生时,仅仅只是利用合成线程去处理这些变换,而不牵扯到主线程,大大提高渲染效率。当然这个变化不限于tranform,任何可以实现合成效果的CSS属性都能用will-change来声明。
这里有一个实际的例子,一行will-change: tranform拯救一个项目。