浏览器重绘与重排的性能优化
2016-11-25 21:54
351 查看
了解了浏览器渲染原理之后
我们知道了浏览器听过渲染树计算布局后,就开始绘制页面元素
但是渲染树并不是一成不变的,在我们的脚本当中
它是可能改变的
浏览器此时需要重新计算元素几何属性
并且页面中其他元素的几何属性可能会受影响
这样渲染树就发生了改变,也就是重新构造RenderTree渲染树
这个过程叫做重排(reflow)
如果DOM变化仅仅影响的了背景色等等非几何属性
此时就发生了重绘(repaint)而不是重排
因为布局没有发生改变
不管页面发生了重绘还是重排,它们都会影响性能(重绘还好一些)
能避免要尽量避免
下列情况会发生重排
页面初始渲染
添加/删除可见DOM元素
改变元素位置
改变元素尺寸(宽、高、内外边距、边框等)
改变元素内容(文本或图片等)
改变窗口尺寸
不同的条件下发生重排的范围及程度会不同
某些情况甚至会重排整个页面,比如滑动滚动条
比如我们想用js中修改一个div元素的样式
写下了以下代码
我们修改了元素的left、top、width、height属性
满足我们发生重排的条件
理论上会发生4次重排
但是实际上只会发生1次重排
这是因为我们现代的浏览器都有渲染队列的机制
当我改变了元素的一个样式会导致浏览器发生重排或重绘时
它会进入一个渲染队列
然后浏览器继续往下看,如果下面还有样式修改
那么同样入队
直到下面没有样式修改
浏览器会按照渲染队列批量执行来优化重排过程,一并修改样式
这样就把本该4次的重排优化为1次
但是我们现在想要修改样式后在控制台打印
千万不要写这样的代码,因为发生了4次重排
有同学可能不懂了,不是说浏览器有渲染队列优化机制吗?
为什么这样写就会发生4次重排
因为offsetLeft/Top/Width/Height非常叼
它们会强制刷新队列要求样式修改任务立刻执行
想一想其实这么做是有道理的
毕竟浏览器不确定在接下来的代码中你是否还会修改同样的样式
为了保证获得正确的值,它不得不立刻执行渲染队列触发重排(错的不是我,是这个世界)
以下属性或方法会刷新渲染队列
offsetTop、offsetLeft、offsetWidth、offsetHeight
clientTop、clientLeft、clientWidth、clientHeight
scrollTop、scrollLeft、scrollWidth、scrollHeight
getComputedStyle()(IE中currentStyle)
我们在修改样式过程中,要尽量避免使用上面的属性
这样就仅仅发生1次重排了,原因相信大家已经很清晰了
把所有的读操作移到所有写操作之后
效率高多了
这是其中一种优化的方法
虽然现代浏览器有渲染队列的优化机制
但是古董浏览器效率仍然底下,触发了4次重排
即便这样,我们仍然可以做出优化
我们需要cssText属性合并所有样式改变
这样只需要修改DOM一次一并处理
仅仅触发了1次重排
而且只用了一行代码,看起来相对干净一些
不过有一点要注意,cssText会覆盖已有的行间样式
如果想保留原有行间样式,这样做
除了cssText以外,我们还可以通过修改class类名来进行样式修改
这种办法可维护性好,还可以帮助我们免除显示性代码
(有一点点性能影响,改变class需要检查级联样式,不过瑕不掩瑜)
这种读操作完就执行写操作造成了2次重排
缓存可以进行优化
这也相当于是分离读写操作了
优化为1次重排
(如果ul还不存在,最好的办法是先循环添加li到ul,然后再把ul添加到文档,1次重排)
我可以做出下面的优化
上面的方法减少重绘和重排的原理很简单
元素脱离文档
改变样式
元素回归文档
而改变元素就分别使用了隐藏元素、文档碎片和克隆元素
上面的方法我认为仅仅是理论上可以优化重排重绘次数
现代浏览器的优化可能会超过我们的想象
==主页传送门==
我们知道了浏览器听过渲染树计算布局后,就开始绘制页面元素
但是渲染树并不是一成不变的,在我们的脚本当中
它是可能改变的
重绘与重排
当DOM变化影响了元素的几何属性(宽、高改变等等)浏览器此时需要重新计算元素几何属性
并且页面中其他元素的几何属性可能会受影响
这样渲染树就发生了改变,也就是重新构造RenderTree渲染树
这个过程叫做重排(reflow)
如果DOM变化仅仅影响的了背景色等等非几何属性
此时就发生了重绘(repaint)而不是重排
因为布局没有发生改变
不管页面发生了重绘还是重排,它们都会影响性能(重绘还好一些)
能避免要尽量避免
触发重排
页面布局和元素几何属性的改变就会导致重排下列情况会发生重排
页面初始渲染
添加/删除可见DOM元素
改变元素位置
改变元素尺寸(宽、高、内外边距、边框等)
改变元素内容(文本或图片等)
改变窗口尺寸
不同的条件下发生重排的范围及程度会不同
某些情况甚至会重排整个页面,比如滑动滚动条
浏览器的优化:渲染队列
举个小例子比如我们想用js中修改一个div元素的样式
写下了以下代码
div.style.left = '10px'; div.style.top = '10px'; div.style.width = '20px'; div.style.height = '20px';
我们修改了元素的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);
千万不要写这样的代码,因为发生了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次重排了,原因相信大家已经很清晰了
把所有的读操作移到所有写操作之后
效率高多了
这是其中一种优化的方法
样式集中改变
还是我们最初修改样式的代码div.style.left = '10px'; div.style.top = '10px'; div.style.width = '20px'; div.style.height = '20px';
虽然现代浏览器有渲染队列的优化机制
但是古董浏览器效率仍然底下,触发了4次重排
即便这样,我们仍然可以做出优化
我们需要cssText属性合并所有样式改变
div.style.cssText = 'left:10px;top:10px;width:20px;height:20px;';
这样只需要修改DOM一次一并处理
仅仅触发了1次重排
而且只用了一行代码,看起来相对干净一些
不过有一点要注意,cssText会覆盖已有的行间样式
如果想保留原有行间样式,这样做
div.style.cssText += ';left:10px;';
除了cssText以外,我们还可以通过修改class类名来进行样式修改
div.className = 'new-class';
这种办法可维护性好,还可以帮助我们免除显示性代码
(有一点点性能影响,改变class需要检查级联样式,不过瑕不掩瑜)
缓存布局信息
我觉得缓存真是万金油,哪种性能优化都少不了它div.style.left = div.offsetLeft + 1 + 'px'; div.style.top = div.offsetTop + 1 + 'px';
这种读操作完就执行写操作造成了2次重排
缓存可以进行优化
var curLeft = div.offsetLeft; var curTop = div.offsetTop; div.style.left = curLeft + 1 + 'px'; div.style.top = curTop + 1 + 'px';
这也相当于是分离读写操作了
优化为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); }
我可以做出下面的优化
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'; <--
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); <--
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); <--
上面的方法减少重绘和重排的原理很简单
元素脱离文档
改变样式
元素回归文档
而改变元素就分别使用了隐藏元素、文档碎片和克隆元素
上面的方法我认为仅仅是理论上可以优化重排重绘次数
现代浏览器的优化可能会超过我们的想象
==主页传送门==
相关文章推荐
- 网站性能优化 兼论浏览器的重绘与重排
- 浏览器重绘与重排的性能优化
- 优化CSS重排重绘与浏览器性能
- 优化CSS重排重绘与浏览器性能
- 重绘与重排及它的性能优化
- 页面的重绘与重排原理以及性能优化
- 浅谈DOM的操作以及性能优化问题-重绘重排
- 关于DOM的操作以及性能优化问题-重绘重排
- 前端性能优化--为什么DOM操作慢? 浅谈DOM的操作以及性能优化问题-重绘重排 为什么要减少DOM操作 为什么要减少操作DOM
- reflow repaint 小心重绘与回流,优化浏览器性能
- ASP.NET性能优化之让浏览器缓存动态网页
- ASP.NET性能优化之让浏览器缓存动态网页
- 浏览器的加载与页面性能优化
- 高性能javascript笔记:浏览器中DOM操作的性能优化(二)
- 浏览器的加载与页面性能优化
- 浏览器的加载与页面性能优化
- 浏览器的加载与页面性能优化
- 高性能javascript笔记:浏览器中DOM操作的性能优化(一)
- 浏览器的加载与页面性能优化
- [WebKit]浏览器的加载与页面性能优化