ChinaGuanq 2019-06-26
回流(reflow),重绘(repaint)都是浏览器更新页面视图的方式,区别在于:
repaint和reflow是DOM操作影响性能的主要原因。一个节点触发了repaint,浏览器会检查DOM Tree中其他所有节点的显示方式;一个节点触发了reflow会导致它的祖先节点,后代节点以及在它之后的节点全部reflow。reflow对性能的影响大于repaint。
一个前端开发人员对这些概念一无所知是一件很恐怖的事情,想想他在写代码的时候不知道哪些操作会对性能造成影响,可能会出现这种情况:
var toChange = document.getElementById('target'); toChange.style.background = '#333'; toChange.style.color = '#fff'; toChange.style.border = '1px solid #00f';
这无疑是可以优化的,下面我们来看看如何减少reflow,repaint的次数。
我们无力去改变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);
再比如像上面style那个例子,如果已知变化后样式,将这些样式写成一个class,再去改变元素的class或者直接修改cssText属性,这两种方式都将多次reflow缩减为一次。
对复制品进行操作也是一种解决方案,比如需要对已有节点进行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,在第一次调用时存起来也是复制的一种。
在做好了其他优化措施的前提下想要进一步提升性能有时需要舍弃,比如减小动画帧数,即增大动画函数执行的间隔,这样动画流畅程度会降低,但整个应用的性能得到了提升。
最后,我认为理解reflow和repaint的原理及触发情况是十分重要的,在写每一行代码时都应该明确它对性能的影响。
参考文章:
REFLOWS & REPAINTS: CSS PERFORMANCE MAKING YOUR JAVASCRIPT SLOW?