重绘和回流以及如何优化 (2)

最核心的思想就是对于对引起回流和重绘的操作不要一次一次的修改,应该用一个容器装起来,一次性修改
比如修改样式如下:

const el = document.getElementById('test'); el.style.padding = '5px'; el.style.borderLeft = '1px'; el.style.borderRight = '2px'; //可以优化为使用cssText或者class统一添加 const el = document.getElementById('test'); el.style.cssText += 'border-left: 1px; border-right: 2px; padding: 5px;'; //或者 const el = document.getElementById('test'); el.className += ' active';

再比如增加DOM节点:
考虑我们要执行一段批量插入节点的代码:

function appendDataToElement(appendToElement, data) { let li; for (let i = 0; i < data.length; i++) { li = document.createElement('li'); li.textContent = 'text'; appendToElement.appendChild(li); } } const ul = document.getElementById('list'); appendDataToElement(ul, data);

如上面这种写法会导致多次回流
优化方式可以用document.createDocumentFragment

const ul = document.getElementById('list'); const fragment = document.createDocumentFragment(); appendDataToElement(fragment, data); ul.appendChild(fragment);

前面有说到渲染树回流、重绘都是针对可见元素,那么就有以下优化方案
原理就是利用display:none隐藏元素,进行各种增删改元素的操作,操作完再使其可见,对display:none隐藏元素进行操作是不会引起回流的。下面代码只会在ul显示的时候(display:block的时候)进行一次回流。

function appendDataToElement(appendToElement, data) { let li; for (let i = 0; i < data.length; i++) { li = document.createElement('li'); li.textContent = 'text'; appendToElement.appendChild(li); } } const ul = document.getElementById('list'); ul.style.display = 'none'; appendDataToElement(ul, data); ul.style.display = 'block'; 5.2、避免触发同步UI渲染

在前面浏览器优化那一章中,提到那些关于获取节点(元素)布局信息的属性不能过分使用,会引起浏览器的强制回流或重绘,也就是在同步中进行回流重绘,所以我们也需要减少对这类属性的使用。
例子如下

function initP() { for (let i = 0; i < paragraphs.length; i++) { paragraphs[i].style.width = box.offsetWidth + 'px'; } }

这段代码看上去是没有什么问题,可是其实会造成很大的性能问题。在每次循环的时候,都读取了box的一个offsetWidth属性值,然后利用它来更新p标签的width属性。这就导致了每一次循环的时候,浏览器都必须先使上一次循环中的样式更新操作生效,才能响应本次循环的样式读取操作。每一次循环都会强制浏览器刷新队列。我们可以优化为:

const width = box.offsetWidth; function initP() { for (let i = 0; i < paragraphs.length; i++) { paragraphs[i].style.width = width + 'px'; } } 5.3、对于复杂动画效果,使用绝对定位让其脱离文档流

对于复杂动画效果,由于会经常的引起回流重绘,因此,我们可以使用绝对定位,让它脱离文档流。否则会引起父元素以及后续元素频繁的回流
可以看这个例子,可以打开调试面板查看FPS的变化

例子中在未优化前,元素未绝对定位,依靠margin的变化进行动画,引起大量的回流,而当使用了绝对定位,明显感觉FPS稳定在每秒60次,也就是16.6ms一次。

5.4、css3硬件加速(GPU加速)

比起考虑如何减少回流重绘,我们更期望的是,根本不要回流重绘。这个时候,css3硬件加速就闪亮登场啦!!
划重点:

使用css3硬件加速,可以让transform、opacity、filters这些动画不会引起回流重绘 。

对于动画的其它属性,比如background-color这些,还是会引起回流重绘的,不过它还是可以提升这些动画的性能
如何使用
常见的触发硬件加速的css属性:
transform
opacity
filters
Will-change
效果
我们可以先看个例子。我通过使用chrome的Performance捕获了动画一段时间里的回流重绘情况,实际结果如下图:

重绘和回流以及如何优化

从图中我们可以看出,在动画进行的时候,没有发生任何的回流重绘。如果感兴趣你也可以自己做下实验。
重点
使用css3硬件加速,可以让transform、opacity、filters这些动画不会引起回流重绘
对于动画的其它属性,比如background-color这些,还是会引起回流重绘的,不过它还是可以提升这些动画的性能。
css3硬件加速的坑
当然,任何美好的东西都是会有对应的代价的,过犹不及。css3硬件加速还是有坑的:
如果你为太多元素使用css3硬件加速,会导致内存占用较大,会有性能问题。
在GPU渲染字体会导致抗锯齿无效。这是因为GPU和CPU的算法不同。因此如果你不在动画结束的时候关闭硬件加速,会产生字体模糊。
参考文章链接:
https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/24
https://www.zhangxinxu.com/wordpress/2013/09/css3-animation-requestanimationframe-tween-%e5%8a%a8%e7%94%bb%e7%ae%97%e6%b3%95/

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wspzxg.html