const e1 = document.getElementById('test');
e1.style.padding = '5px';
e1.style.borderLeft = '1px';
e1.style.borderRight = '2px';
上面例子中,三个样式被修改(几何结构改变),每一个都会引起回流。当然,大部分浏览器都做了优化,只会触发一次重排。但是如果旧版浏览器浏览或者在上面代码执行时,有其他代码访问了布局信息(上文中会触发回流的布局信息),那么就会导致三次重排
我们可以合并所有的改变然后一次处理,比如可以采取以下方式:
1.使用cssText
const e1 = document.getElementById('test');
e1.style.cssText += 'border-left: 1px; border-right: 2px; padding: 5px'
cssText的使用优势
一般情况下我们用js设置元素对象的样式会使用这样的形式:
var element= document.getElementById(“id”);
element.style.width=”20px”;
element.style.height=”20px”;
element.style.border=”solid 1px red”;
样式一多,代码就很多;而且通过JS来覆写对象的样式是比较典型的一种销毁原样式并重建的过程,这种销毁和重建,都会增加浏览器的开销。
js中有一个cssText的方法:
语法为:
obj.style.cssText=”样式”;
element.style.cssText=”width:20px;height:20px;border:solid 1px red;”;
这样就可以尽量避免页面reflow,提高页面性能。
但是,这样会有一个问题,会把原有的cssText清掉,比如原来的style中有’display:none;’,那么执行完上面的JS后,display就被删掉了。
为了解决这个问题,可以采用cssText累加的方法:
Element.style.cssText += ‘width:100px;height:100px;top:100px;left:100px;’
因此,上面cssText累加的方法在IE中是无效的。
最后,可以在前面添加一个分号来解决这个问题:
Element.style.cssText += ‘;width:100px;height:100px;top:100px;left:100px;’
再进一步,如果前面有样式表文件写着 div { text-decoration:underline; },这个会被覆盖吗?不会!因为它不是直接作用于 HTML 元素的 style 属性。
2.修改css的class
const e1 = document.getElementById('test');
e1.className += 'active'
需要提前写好active的样式
批量修改dom
当需要对dom进行一系列修改的时候,可以通过以下步骤减少回流重绘次数:
1.使元素脱离文档流
2.对其进行多次修改
3.将元素带回到文档中
该过程第一和第三步可能会引起回流,但经过第一步之后,对dom的所有修改都不会引起回流,因为他已经不在渲染树中了。
有三种方式可以让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 u1 = document.getElementById('list')
appendDataToElement(u1, data)
1.隐藏元素,应用修改,重新显示
这样在显示和隐藏节点的时候,产生两次重绘:
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 u1 = document.getElementById('list')
u1.style.display = 'none';
appendDataToElement(u1, data);
u1.style,display = 'block';
注意display属性是block
2.使用文档片段(document fragment)在当前dom之外构建一个子树,再把它拷贝回文档。
const u1 = document.getElementById('test')
const fragment = document.createDocumentFragment();
appendDataToElement(fragment, data);
u1.appendChild(fragment);
3.将原始元素拷贝到一个脱离文档的节点中,修改节点后,再替换原始的元素
const u1 = document.getElementById('list');
const clone = u1.cloneNode(true);
appendDataToElement(clone, data)
u1.parentNode.replaceChild(clone, u1);
对于上述那种情况,我写了一个demo来测试修改前和修改后的性能。然而实验结果不是很理想。
原因:原因其实上面也说过了,浏览器会使用队列来储存多次修改,进行优化,所以对这个优化方案,我们其实不用优先考虑。
避免触发同步布局事件
上文我们说过,当我们访问元素的一些属性的时候,会导致浏览器强制清空队列,进行强制同步布局。举个例子,比如说我们想将一个p标签数组的宽度赋值为一个元素的宽度,我们可能写出这样的代码:
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'
}
}
with + 'px' 这个细节也很巧妙
对于复杂动画效果,使用绝对定位让其脱离文档流
对于复杂动画效果,由于会经常的引起回流重绘,因此,我们可以使用绝对定位,让它脱离文档流。否则会引起父元素以及后续元素频繁的回流。这个我们就直接上个例子。
打开这个例子后,我们可以打开控制台,控制台上会输出当前的帧数(虽然不准)。
从上图中,我们可以看到,帧数一直都没到60。这个时候,只要我们点击一下那个按钮,把这个元素设置为绝对定位,帧数就可以稳定60。
css3硬件加速(GPU加速)
比起考虑如何减少回流重绘,我们更期望的是,根本不要回流重绘。这个时候,css3硬件加速就闪亮登场啦!!
划重点:使用css3硬件加速,可以让transform、opacity、filters这些动画不会引起回流重绘 。但是对于动画的其它属性,比如background-color这些,还是会引起回流重绘的,不过它还是可以提升这些动画的性能。
本篇文章只讨论如何使用,暂不考虑其原理,之后有空会另外开篇文章说明。
1、如何使用
常见的触发硬件加速的css属性:
3、重点
使用css3硬件加速,可以让transform、opacity、filters这些动画不会引起回流重绘。
对于动画的其它属性,比如background-color这些,还是会引起回流重绘的,不过它还是可以提升这些动画的性能。
4、css3硬件加速的坑
如果你为太多元素使用css3硬件加速,会导致内存占用较大,会有性能问题。
在GPU渲染字体会导致抗锯齿无效。这是因为GPU和CPU的算法不同。因此如果你不在动画结束的时候关闭硬件加速,会产生字体模糊。
总结
本文主要讲了浏览器的渲染过程、浏览器的优化机制以及如何减少甚至避免回流和重绘,希望可以帮助大家更好的理解回流重绘。