前端性能:回流与重绘的学习
浏览器
- 浏览器使用流式布局模型
- 浏览器将html解析成DOM,把css解析成CSSOM,DOM和CSSOM合并产生渲染树RenderTree
- 根据RenderTree,浏览器计算元素在页面上的大小和位置,然后把节点绘制在页面上
- 流式布局的特点:一般对RenderTree遍历一次就可以完成页面渲染。但是table及其内部元素可能要进行多次运算才能完成渲染。
回流
当渲染树中部分或全部元素的尺寸、结构、边距等某些属性发生改变时,浏览器重新渲染部分或者全部文档的过程
导致回流的操作:
- 页面首次渲染、浏览器窗口大小发生改变
- 元素几何属性发生变化,尺寸或者位置发生改变
- 元素内容发生改变,比如文字数量或者图片大小等
- 元素字体大小变化(应该是不算c3?)
- DOM树结构变化,添加或者删除可见的DOM元素
- CSS伪类激活,例如hover(?真的吗)
- 获取某些属性,offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、 clientTop、clientLeft、clientWidth、clientHeight、getComputedStyle() (currentStyle in IE)。所以,在多次使用这些值时应进行缓存。
重绘
当元素的一部分属性发生变化,如color、background-color、visibility等,不会影响元素在文档流中的位置,不需要重新渲染,浏览器只是将新样式赋给元素并重新绘制它
var s = document.body.style;
s.padding = "2px"; // 回流+重绘
s.border = "1px solid red"; // 再一次 回流+重绘
s.color = "blue"; // 再一次重绘
s.backgroundColor = "#ccc"; // 再一次 重绘
s.fontSize = "14px"; // 再一次 回流+重绘
// 添加node,再一次 回流+重绘
document.body.appendChild(document.createTextNode('abc!'));
如何避免
浏览器应对机制:队列中的操作到了一定的数量或者到了一定的时间间隔,就会flush队列,进行批量处理。
- 控制
尽量减少repaint和reflow的次数,并且repaint对性能的影响小于reflow。 - 脱离
动画时时刻刻都在操作着DOM,为了避免动画使得其他节点也在时时刻刻reflow,可以将动画所在的元素设为position: fixed或者position: absolute,使其脱离文档流,这个元素reflow时不会影响其他节点的布局,虽然还是会产生repaint,但相对来说得到了优化。 - 合并
能一次完成的操作就不要分两次。添加多个节点时使用DocumentFragment,处理完之后再一起更新:
var docFragm = document.createDocumentFragment();
var elem, contents;
for(var i = 0; i < textlist.length; i++) {
elem = document.createElement('p');
contents = document.createTextNode(textlist[i]);
elem.appendChild(contents);
docFragm.appendChild(elem);
}
document.body.appendChild(docFragm);
- 复制
对复制品进行操作,对已有节点进行DOM操作使用cloneNode():
var original = document.getElementById('container');
var cloned = original.cloneNode(true);
cloned.setAttribute('width', '50%');
var elem, contents;
for(var i = 0; i < textlist.length; i++) {
elem = document.createElement('p');
contents = document.createTextNode(textlist[i]);
elem.appendChild(contents);
cloned.appendChild(elem);
}
//先复制,再进行替代
original.parentNode.replaceChild(cloned, original);
使用cloneNode()要注意的是,唯一的参数代表是否进行深复制。另外cloneNode()无法复制事件监听函数,以及表单控件的value。
又比如获取offsetWidth,getComputedStyle()这些取值操作每次都会触发reflow,在第一次调用时存起来也是复制的一种。
- 舍弃
在做好了其他优化措施的前提下想要进一步提升性能有时需要舍弃,比如减小动画帧数,即增大动画函数执行的间隔,这样动画流畅程度会降低,但整个应用的性能得到了提升。 - 分类归纳
CSS:
- 避免使用table布局。
- 尽可能在DOM树的最末端改变class。
- 避免设置多层内联样式。
- 将动画效果应用到position属性为absolute或fixed的元素上。
- 避免使用CSS表达式(例如:calc())。
javascript:
- 避免频繁操作样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性。
- 避免频繁操作DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中。
- 也可以先为元素设置display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。
- 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
- 对具有复杂动画的元素使用position:absolute,使它脱离文档流,否则会引起父元素及后续元素频繁回流。
回流一定产生重绘,重绘不一定产生回流