浏览器重绘与重排的性能优化

重绘与重排

当DOM变化影响了元素的几何属性(宽、高改变等等) 
浏览器此时需要重新计算元素几何属性 
并且页面中其他元素的几何属性可能会受影响 
这样渲染树就发生了改变,也就是重新构造RenderTree渲染树 
这个过程叫做重排(reflow)

如果DOM变化仅仅影响的了背景色等等非几何属性 
此时就发生了重绘(repaint)而不是重排 
因为布局没有发生改变


不管页面发生了重绘还是重排,它们都会影响性能(重绘还好一些) 
能避免要尽量避免

触发重排

页面布局和元素几何属性的改变就会导致重排 
下列情况会发生重排

  • 页面初始渲染
  • 添加/删除可见DOM元素
  • 改变元素位置
  • 改变元素尺寸(宽、高、内外边距、边框等)
  • 改变元素内容(文本或图片等)
  • 改变窗口尺寸

不同的条件下发生重排的范围及程度会不同 
某些情况甚至会重排整个页面,比如滑动滚动条

浏览器的优化:渲染队列

举个小例子 
比如我们想用js中修改一个div元素的样式 
写下了以下代码

div.style.left = '10px';
div.style.top = '10px';
div.style.width = '20px';
div.style.height = '20px';
 
 
  • 1
  • 2
  • 3
  • 4

我们修改了元素的left、top、width、height属性 
满足我们发生重排的条件 
理论上会发生4次重排 
但是实际上只会发生1次重排 
这是因为我们现代的浏览器都有渲染队列的机制 
当我改变了元素的一个样式会导致浏览器发生重排或重绘时 
它会进入一个渲染队列 
然后浏览器继续往下看,如果下面还有样式修改 
那么同样入队 
直到下面没有样式修改 
浏览器会按照渲染队列批量执行来优化重排过程,一并修改样式 
这样就把本该4次的重排优化为1次


但是我们现在想要修改样式后在控制台打印

div.style.left = '10px';
console.log(div.offsetLeft);
div.style.top = '10px';
console.log(div.offsetTop);
div.style.width = '20px';
console.log(div.offsetWidth);
div.style.height = '20px';
console.log(div.offsetHeight);
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

千万不要写这样的代码,因为发生了4次重排 
有同学可能不懂了,不是说浏览器有渲染队列优化机制吗? 
为什么这样写就会发生4次重排 
因为offsetLeft/Top/Width/Height非常叼 
它们会强制刷新队列要求样式修改任务立刻执行 
想一想其实这么做是有道理的 
毕竟浏览器不确定在接下来的代码中你是否还会修改同样的样式 
为了保证获得正确的值,它不得不立刻执行渲染队列触发重排(错的不是我,是这个世界)


以下属性或方法会刷新渲染队列

  • offsetTop、offsetLeft、offsetWidth、offsetHeight
  • clientTop、clientLeft、clientWidth、clientHeight
  • scrollTop、scrollLeft、scrollWidth、scrollHeight
  • getComputedStyle()(IE中currentStyle)

我们在修改样式过程中,要尽量避免使用上面的属性

重绘与重排的性能优化

分离读写操作

了解了原理我们就可以对上面的代码进行优化

div.style.left = '10px';
div.style.top = '10px';
div.style.width = '20px';
div.style.height = '20px';
console.log(div.offsetLeft);
console.log(div.offsetTop);
console.log(div.offsetWidth);
console.log(div.offsetHeight);
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这样就仅仅发生1次重排了,原因相信大家已经很清晰了 
把所有的读操作移到所有写操作之后 
效率高多了 
这是其中一种优化的方法

样式集中改变

还是我们最初修改样式的代码

div.style.left = '10px';
div.style.top = '10px';
div.style.width = '20px';
div.style.height = '20px';
 
 
  • 1
  • 2
  • 3
  • 4

虽然现代浏览器有渲染队列的优化机制 
但是古董浏览器效率仍然底下,触发了4次重排 
即便这样,我们仍然可以做出优化 
我们需要cssText属性合并所有样式改变

div.style.cssText = 'left:10px;top:10px;width:20px;height:20px;';
 
 
  • 1

这样只需要修改DOM一次一并处理 
仅仅触发了1次重排 
而且只用了一行代码,看起来相对干净一些

不过有一点要注意,cssText会覆盖已有的行间样式 
如果想保留原有行间样式,这样做

div.style.cssText += ';left:10px;';
 
 
  • 1

除了cssText以外,我们还可以通过修改class类名来进行样式修改

div.className = 'new-class';
 
 
  • 1

这种办法可维护性好,还可以帮助我们免除显示性代码 
(有一点点性能影响,改变class需要检查级联样式,不过瑕不掩瑜)

缓存布局信息

我觉得缓存真是万金油,哪种性能优化都少不了它

div.style.left = div.offsetLeft + 1 + 'px';
div.style.top = div.offsetTop + 1 + 'px';
 
 
  • 1
  • 2

这种读操作完就执行写操作造成了2次重排 
缓存可以进行优化

var curLeft = div.offsetLeft;
var curTop = div.offsetTop;
div.style.left = curLeft + 1 + 'px';
div.style.top = curTop + 1 + 'px';
 
 
  • 1
  • 2
  • 3
  • 4

这也相当于是分离读写操作了 
优化为1次重排

元素批量修改

现在我们想要向ul中循环添加大量li 
(如果ul还不存在,最好的办法是先循环添加li到ul,然后再把ul添加到文档,1次重排)

var ul = document.getElementById('demo');
for(var i = 0; i < 1e5; i++){
    var li = document.createElement('li');
    var text = document.createTextNode(i);
    li.appendChild(text);
    ul.appendChild(li);
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

我可以做出下面的优化

var ul = document.getElementById('demo');
ul.style.display = 'none'; <--
for(var i = 0; i < 1e5; i++){
    var li = document.createElement('li');
    var text = document.createTextNode(i);
    li.appendChild(text);
    ul.appendChild(li);
}
ul.style.display = 'block'; <--
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
var ul = document.getElementById('demo');
var frg = document.createDocumentFragment(); <--
for(var i = 0; i < 1e5; i++){
    var li = document.createElement('li');
    var text = document.createTextNode(i);
    li.appendChild(text);
    frg.appendChild(li); <--
}
ul.appendChild(frg); <--
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
var ul = document.getElementById('demo');
var clone = ul.cloneNode(true); <--
for(var i = 0; i < 1e5; i++){
    var li = document.createElement('li');
    var text = document.createTextNode(i);
    li.appendChild(text);
    clone.appendChild(li); <--
}
ul.parentNode.replaceChild(clone,ul); <--
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

上面的方法减少重绘和重排的原理很简单

  • 元素脱离文档
  • 改变样式
  • 元素回归文档

而改变元素就分别使用了隐藏元素、文档碎片和克隆元素 
上面的方法我认为仅仅是理论上可以优化重排重绘次数 
现代浏览器的优化可能会超过我们的想象


转载自:http://blog.csdn.net/q1056843325/article/details/53340308

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
重绘重排是CSS渲染过程中的两个要概念。 重绘(Repaint)指的是当元素的样式发生变化,但不影响其布局时,浏览器会将新样式应用到元素上,新绘制元素的外观。重绘的开销相对较小,不会引起布局的变化。 而重排(Reflow)指的是当页面布局发生变化时,例如修改了元素的尺寸、位置、内容等,浏览器新计算并更新元素的几何属性(如大小、位置),然后布局页面。重排的开销相对较大,因为它涉及到整个页面或部分页面的新渲染。 重绘重排的区别在于是否引起布局的变化。重绘只会新绘制元素的外观,而不会影响其周围元素的布局;而重排会导致整个渲染树的新构建和布局。 在性能优化方面,我们通常要尽量减少重排重绘的次数,因为它们会消耗大量的计算资源。一些常见的优化方法包括: 1. 使用 CSS3 动画或过渡代替 JavaScript 实现的动画效果,因为后者可能会导致频繁的重排重绘; 2. 使用类似 flexbox 和 grid 等布局技术,可以减少页面布局的复杂性,降低重排重绘的次数; 3. 避免频繁访问会引起重排重绘的属性,例如 offsetTop、offsetLeft、scrollTop、clientWidth 等; 4. 批量更新样式或布局,可以使用 CSS 类名的方式一次性修改多个元素的样式,而不是逐个修改; 5. 将需要执行多次重排DOM 操作尽量合并为一次,使用文档片段(DocumentFragment)进行缓存。 通过合理优化和减少重排重绘的次数,可以提升页面的性能和响应速度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值