reflow和repaint在pc端只要不是怀有明知山有虎,偏向虎山行的心态写代码,这两货几乎不会引起性能问题, 可是移动端的渲染能力和pc端差了不止一个大截,一个不当心reflow和repaint就成了移动端的“性能杀手”。因此了解reflow和repaint也是颇有必要的,在考量页面性能的时候分析reflow和repaint也算是一个切入点。javascript
reflow
回流,或者叫重排均可以。回流(reflow)这个名词指的是浏览器为了从新渲染部分或所有的文档而从新计算文档中元素的位置和几何结构的过程。css
简单来讲就是当页面布局或者几何属性改变时就须要reflow。html
在一个页面中至少在页面刚加载的时候有一次reflow,在reflow的过程当中浏览器会将render tree中受影响的节点失效,再从新构建render tree,有时候,即便仅仅回流一个单一的元素,也可能要求它的父元素以及任何跟随它的元素也产生回流java
repaint
重绘,当页面中的元素只须要更新样式风格不影响布局,好比更换背景色background-color,这个过程就是重绘。react
从reflow的定义中就能够听出一些来,元素的布局和几何属性改变时就会触发reflow。主要有这些属性:程序员
盒模型相关的属性: width,height,margin,display,border,etcweb
定位属性及浮动相关的属性: top,position,float,etcchrome
改变节点内部文字结构也会触发回流: text-align, overflow, font-size, line-height, vertival-align,etccanvas
除开这三大类的属性变更会触发reflow,如下状况也会触发:浏览器
页面中的元素更新样式风格相关的属性时就会触发重绘,如background,color,cursor,visibility,etc
注意:由页面的渲染过程可知,reflow必将会引发repaint,而repaint不必定会引发reflow
了解有哪些属性值改变会触发回流或者重绘点击这里
设想一个这样的场景,咱们须要在一个循环中不断修改一个dom节点的位置或者是内容
document.addEventListener('DOMContentLoaded', function () {
var date = new Date();
for (var i = 0; i < 70000; i++) {
var tmpNode = document.createElement("div");
tmpNode.innerHTML = "test" + i;
document.body.appendChild(tmpNode);
}
console.log(new Date() - date);
});
复制代码
这里屡次测量消耗时间大概在500ms(运行环境均为pc端,小霸王笔记本)。看到这个结果可能就有疑问了,这里有70000次内容的修改,就有70000reflow操做,也就用了500ms的时间(归功于迟缓的dom操做),说好的reflow消耗性能呢。
犯二代码以下:
document.addEventListener('DOMContentLoaded', function () {
var date = new Date();
for (var i = 0; i < 70000; i++) {
var tmpNode = document.createElement("div");
tmpNode.innerHTML = "test" + i;
document.body.offsetHeight; // 获取body的真实值
document.body.appendChild(tmpNode);
}
console.log("speed time", new Date() - date);
});
复制代码
通常人应该不会去运行这种代码,若是你运行了的话,恭喜你的电脑-1s。可是若是没有衡量指标,优化性能也就无从谈起。
“If you cannot measure it, you cannot improve it.” -Lord Kelvin
为了防止浏览器假死,把循环次数都改成7000次,得出的结果是(屡次平均):
经过这两个样例印证了浏览器确实有优化reflow的小动做,聪明的程序员不会依赖浏览器的优化策略,在平常开发中遇到for循环就应该慎重编写循环体内部的代码。
如何减小reflow和repaint呢?回到定义去,reflow在页面布局或者定位发生变化时才会发生
,从定义中咱们至少能够得出两个优化思路
其本质上为减小对render tree的操做。render tree也就是渲染树,它的每一个节点都是可见,且包含该节点的内容和对应的规则样式,这也是render tree和dom数最大的区别所在, 减小reflow操做,主旨是合并多个reflow,最后再反馈到render tree中,诸如:
// 很差的写法
var left = 1;
var top = 2;
ele.style.left = left + "px";
ele.style.top = top + "px";
// 比较好的写法
ele.className += " className1";
复制代码
或者直接修改cssText:
ele.style.cssText += ";
left: " + left + "px;
top: " + top + "px;";
复制代码
Dom规定文档片断(document fragment)是一种“轻量级”的文档,能够包含和控制节点,但不会想完整的文档那样占用额外的资源。虽然不能把文档片断直接添加到文档中,可是能够将它做为一个“仓库”来使用,便可以在里面保存未来可能会添加到文档中的节点。 好比最开始的样例结合DocumentFragment就能够这样写:
document.addEventListener('DOMContentLoaded', function () {
var date = new Date(),
fragment = document.createDocumentFragment();
for (var i = 0; i < 7000; i++) {
var tmpNode = document.createElement("div");
tmpNode.innerHTML = "test" + i;
fragment.appendChild(tmpNode);
}
document.body.appendChild(fragment);
console.log("speed time", new Date() - date);
});
复制代码
将多个修改结果收纳到了documentFragment这个“仓库”中,这个过程并不会影响到render tree,待循环完毕再将这个“仓库”的“存货”添加到dom上,以此达到减小reflow的目的,使用cloneNode也是同理。 而使用display:none来下降reflow的性能开销的原理在于使节点从render tree中失效,等通过多个会触发reflow操做后再“上线”
// 很差的写法
for(let i = 0; i < 20; i++ ) {
el.style.left = el.offsetLeft + 5 + "px";
el.style.top = el.offsetTop + 5 + "px";
}
// 比较好的写法
var left = el.offsetLeft,
top = el.offsetTop,
s = el.style;
for (let i = 0; i < 20; i++ ) {
left += 5;
top += 5;
s.left = left + "px";
s.top = top + "px";
}
复制代码
咱们能够将一些会触发回流的属性替换,来避免reflow。好比用translate代替top,用opacity替代visibility
样例代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style> #react { position: relative; top: 0; width: 100px; height: 100px; background-color: red; } </style>
</head>
<body>
<div id="react"></div>
<script type="text/javascript"> setTimeout(() => { document.getElementById("react").style.top = "100px" }, 2000); </script>
</body>
</html>
复制代码
代码很简单,页面上有一个红色的方块,2秒后它的top值将会变为“100px”,为了方便体现替代的属性能够避免reflow这里咱们使用chrome的开发者工具,部分截图以下
咱们把这五个过程用时记下:80 + Layout(73) + 72 + 20 + 69 = 316us
再用translate替代top:
- position: relative;
- top: 0;
+ transform: translateY(0);
- document.getElementById("react").style.top = "100px"
+ document.getElementById("react").style.transform = "translateY(100px)"
复制代码
Performace截图:
咱们再用opacity去替代visibility试试看。
- document.getElementById("react").style.transform = "translateY(100px)"
+ document.getElementById("react").style.visibility = "hidden"
复制代码
Performace截图:
+ opacity: 1;
- document.getElementById("react").style.visibility = "hidden"
+ document.getElementById("react").style.opacity = "0"
复制代码
按照上面的样例,应该得出用opacity替代visibility后重绘也就是Paint这个过程会消失从而达到性能提高的目的,既然这样咱们来看Performace截图:
其实opacity变化并不能改变这个图层的内容,改变的只是当前图层的alpha通道的值,从而来决定这个图层显不显示。可是这opacity变化的元素并非单独的图层,而是在document这个图层上的,以下Layers截图:
就是说浏览器并不能由于图层里面有一个opacity为0的元素就让整个图层的alpha通道变为零,而让整个图层不显示,因此就有了Layout和Paint这两个过程。解决办法也很简单那就是直接让这个元素单独为一个图层
修改css新建图层有两种办法:
这里咱们用下面一个
+ transform: translateZ(0);
复制代码
Performace截图:
这里因为我变更的元素很是简单,只有一个简单的div,减小Paint过程带来的优化收益并非很明显,若是是Paint过程是毫秒级别减小Paint过程的效果仍是可观的。
由上述两个替代会触发reflow和repaint的属性取得性能优化收益的例子中能够看出,这个方法是可行的,除开第一点减小reflow操做和第二点替换属性之外还有一些方法能够减小reflow和repaint
减小table的使用
动画实现的速度选择
对于动画新建图层
table自带的样式和一些很是方便的特性会方便咱们的开发,可是table也有一些与生俱来的性能缺陷,若是想要修改表格里无论哪个单元格,都会致使整张表格的从新Layout,若是这个表格很大,性能的消耗会有一个上升成本的。
在上一个样例中咱们新建了一个图层实现了opacity替代visibility去减小repaint的可行性,那么图层还有什么其余运用吗?答案是有的,咱们能够将一些频繁重绘回流的DOM元素做为一个图层,那么这个DOM元素的重绘和回流的影响只会在这个图层中,固然若是你为每个元素都建立一个图层那样确定也会聪明反被聪明误,还记得上述的Performance截图中的过程吗,最后一个Composite Layers这个过程就是合并多个图层的,图层过多这个过程会很是耗时,其实这个过程自己也很是耗时,原则上是在必要的状况下才会新建图层来减小重绘和回流的影响范围,到底使不使用就须要开发人员在业务情景中balance. 在Chrome浏览器下能够这样建立图层:
大致思路就是咱们把频繁重绘回流的DOM元素做为一个图层,那么这个DOM元素的重绘和回流的影响只会在这个图层中,来提高性能。举个栗子,咱们打开chrome开发者工具中的Layers,而后打开某网站
简单回顾一下本文,咱们最开始聊了一下reflow和repaint是什么,如何触发它们,接下来谈了一下浏览器在处理它们所采起的策略,最后就是如何避免reflow和repaint带来的性能开销,还补充了一下图层的存在乎义和简单运用。 其实在优化reflow和repaint上就是两点:
https://csstriggers.com
http://blog.csdn.net/luoshengyang/article/details/50661553