前端优化,了解浏览器重排与重绘
2018年3月15日
当DOM变化影响了元素的几何属性(宽、高改变等等)
浏览器此时需要重新计算元素几何属性
并且页面中其他元素的几何属性可能会受影响
这样渲染树就发生了改变,也就是重新构造RenderTree渲染树
这个过程叫做重排(reflow)
如果DOM变化仅仅影响的了背景色等等非几何属性
此时就发生了重绘(repaint)而不是重排
因为布局没有发生改变
页面发生重绘和重排,都会影响性能(重绘还好一些),尽量避免或减少重排
触发重排
页面布局和元素几何属性的改变就会导致重排
下列情况会发生重排
- 页面初始渲染
- 添加/删除可见DOM元素
- 改变元素位置
- 改变元素尺寸(宽、高、内外边距、边框等)
- 改变元素内容(文本或图片等)
- 改变窗口尺寸
不同的条件下发生重排的范围及程度会不同
某些情况甚至会重排整个页面,比如滑动滚动条
浏览器的优化:渲染队列
- 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次重排
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需要检查级联样式,但是符合BEM标准可以更好的解耦,增加可维护性,建议这样写
div.className = 'js-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);
上面的方法减少重绘和重排的原理很简单
- 元素脱离文档
- 改变样式
- 元素回归文档
而改变元素就分别使用了
隐藏元素
文档碎片
克隆元素
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
CSS 箭头和线条代码
2018年4月25日 /*箭头*/ div:before { position: absolute; top: 50%; right: 15px; margin-top: -4px; content: " "; display: inline-block; height: 6px; width: 6px; border-width: 2px 2px 0 0; border-color: #c8c8cd; border-style: solid; -webkit-transform: matrix(.71,.71,-.71,.71,0,0); transform: matrix(.71,.71,-.71,.71,0,0); } /*线条*/ div:after { content: " "; position: absolute; right: 0; top: 0; width: 1px; bottom: 0; border-right: 1px solid #d9d9d9; color: #d9d9d9; -webkit-transform-origin: 100% 0; transf...
- 下一篇
JS 浅谈生成指定范围内随机整数的五种方法
2018年3月8日 JS 浅谈生成指定范围内随机整数的五种方法 Math对象方法random()生成的随机数带有小数点,随机整数的方法有很多种,平时经常用到,所以记录五种简单的方法。 Math.random(); 返回介于 0(包含) ~ 1(不包含) 之间的一个随机数。 如下生成一个0到6的随机数: Math.random()*6 举例生成一个3~9之间的整数看五种方法更直观。 一、基于Math对象方法 ceil() 对数进行上舍入。 公式如下: Math.ceil((Math.random()*(max-min+1))+min-1); 代码如下: Math.ceil((Math.random()*7)+2); 二、基于Math对象方法floor() 对数进行下舍入。 公式如下: Math.floor((Math.random()*(max-min+1))+min); 代码如下: Math.floor((Math.random()*7)+3); 三、基于Math对象方法 round() 对数进行四舍五入。 公式如下: Math.round((Math.rand...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
-
Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8编译安装MySQL8.0.19
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
推荐阅读
最新文章
- SpringBoot2全家桶,快速入门学习开发网站教程
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- MySQL8.0.19开启GTID主从同步CentOS8
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2整合Redis,开启缓存,提高访问速度
- Windows10,CentOS7,CentOS8安装Nodejs环境
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果