结合上图,一个完整的渲染流程大致可总结为如下几个步骤:
定义:当通过JS或css改变了元素的宽度、高度等,修改了元素的几何位置属性,那么浏览器会触发重新布局,解析之后的一系列子阶段,这个过程就叫重排。无疑,重排需要更新完整的渲染流水线,所以开销也是最大的。
触发时机和影响范围:DOM节点信息更改,触发重排时,这个DOM更改程度会决定周边DOM更改范围。
定义:如果修改了元素的背景颜色,并没有引起几何位置的变换,所以就直接进入了绘制阶段,然后执行之后的一系列子阶段,这个过程就叫重绘。相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。
触发时机和影响范围:每一次的dom更改或者css几何属性更改,都会引起一次浏览器的重排/重绘过程,而如果是css的非几何属性更改,则只会引起重绘过程。所以说重排一定会引起重绘,而重绘不一定会引起重排,重绘的开销较小,重排的代价较高。
定义:合成是一种将页面的各个部分分离成层(Layer Tree),分别将它们栅格化,然后在称为“合成线程”的中组合为页面的技术。
触发时机和影响范围:在GUI渲染线程后执行,将GUI渲染线程生成的绘制列表转换为位图,然后发送绘制图块命令 DrawQuad 给浏览器进程,浏览器进程根据 DrawQuad 消息生成页面,将页面显示到显示器上
优点:我们使用了 CSS 的 transform 来实现动画效果,避开了重排和重绘阶段,直接在非主线程上执行合成动画操作。这样的效率是最高的,因为是在非主线程上合成,并没有占用主线程的资源,另外也避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率。
分离读写操作
(现代的浏览器都有渲染队列
的机制)<style>
#box{
width:100px;
height:100px;
border:10px solid #ddd;
}
</style>
<body>
<div id="box"></div>
<script>
//读写分离,一次重排
let box = document.getElementById('box')
box.style.width='200px';//(写)js改变样式,加入渲染队列中,顿一下,查看下一行是否还是修改样式,如果是则再加入到渲染队列,一直到下一行代码不是修改样式为止
box.style.height='200px';//(写)
box.style.margin='10px';//(写)
console.log(box.clientWidth);//(读)
</script>
</body>
<script>
//没做到读写分离,两次重排
box.style.width='200px';//(写)js改变样式,加入渲染队列中,顿一下,下一行不是修改样式的代码,浏览器就会直接渲染一次(重排)
console.log(box.clientWidth);//(读)
box.style.height='200px';//(写)
box.style.margin='10px';//(写)
</script>
缓存布局信息
<script>
//两次重排 ’写‘操作中包含clientWidth属性,会刷新渲染队列
box.style.width = box.clientWidth +10 +’px’;
box.style.height= box.clientHeight +10 +’px’
</script>
<script>
let a=box.clientWidth //(读)缓存布局信息
let b=box.clientHeight//(读)缓存布局信息
//一次重排
box.style.width = a+10 +’px’;(写)
box.style.height= b+10 +’px’(写)
</script>
注意:
offsetTop、offsetLeft、offsetWidth、offsetHeight、clientTop、clientWidth、clientHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、getComputedStyle、currentStyle...会刷新渲染队列
。当下一行代码有这些时,即使下一行是修改样式,也会直接发生重排。
样式集中改变(批量处理)
减少通过JavaScript代码修改元素样式,尽量使用修改class名方式操作样式或动画;动画实现的速度的选择:牺牲平滑度换取速度。
比如实现一个动画,以1个像素为单位移动这样最平滑,但是reflow就会过于频繁,大量消耗CPU资源,如果以3个像素为单位移动则会好很多。开启css3动画硬件加速(GPU加速)
把渲染计算交给GPU。(能用transform做的就不要用其他的,因为transform可以开启硬件加速,而硬件加速可以规避重排。直接跳过重排、重绘,走合成进程) box.style.left='100px' //向右移动100px,一次重排
box.style.ctransform='translateX(200)' //向右移动200px,不会引发重排
读到这里,重排、重绘、合成的概念,以及如何针对它们做前端的性能优化,大家有没有更清楚一点呢?如果文中有哪些地方写的不好、不对的地方,欢迎大家批评指正,感谢您的阅读,今天也是元气满满的一天,一起加油呦!