前端性能优化重绘与重排

1.什么是重排和重绘?

首先我们了解一下前因后果:
浏览器下载完页面所有的资源后,就开始构建DOM树,与此同时还会构建渲染树(Render Tree)
注意:在构建渲染树之前,和DOM树一起被构建的还有Style Tree。DOM树和Style Tree 合并成为渲染树

  • DOM树:表示页面结构
  • 渲染树:表示页面的节点如何显示

在这里插入图片描述
一旦渲染树构建完成,就要开始(paint)页面元素。
这里引伸出了重排的概念:

  • 当DOM的变化引起了元素几何属性的变化 比如改变元素的宽高,元素的位置。导致浏览器必须重新计算元素的几何属性,并且重新构建渲染树,这个重新计算元素属性并且重新构建渲染树的过程叫做DOM回流,也被称之为重排。完成重排后,要将重新构建的渲染树渲染到屏幕上,这个过程叫做重绘
  • 通俗的讲:重排负责元素的几何属性的更新(宽高,位置等)。重绘负责元素的样式更新。
  • 重排必定会带来重绘,但是重绘不一定带来重排。当只是改变某个元素的背景时候,就只发生了重绘,没有设涉及重排。

2.重排触发机制

从重排的概念中我们知道,重排发生的根本原理就是元素的几何属性发生变化

  1. 添加或者删除可见的DOM元素(注意是可见的)
  2. 元素位置变化
  3. 元素的尺寸发生变化(例如宽高)
  4. 元素的内容改变
  5. 页面渲染器初始化
  6. 浏览器窗口大小发生改变

3.性能优化

想必大家都知道,为什么要了解重排和重排,因为其严重影响我们网站的性能,代价是非常大的。如果我们不停的改变页面的布局,就会造成浏览器消耗大量的开销在计算页面上,造成页面加载卡顿的情况。用户体验也是极差的。

怎么减少重排的发生呢?

  1. 关于改变样式
var div = document.querySelector('.div');
div.style.width = '100px';
div.style.background = 'red';
div.style.height = '200px';

我们可以看出,上述代码会触发浏览器两次重排,然而最高效的方法就是合并所有的改变一次处理。这样只修改DOM节点一次。如:cssText属性

 var div = document.querySelector('.div');
 div.style.cssText = 'width:100px;background: red; height:200px';

爱思考的朋友们可能就会发现,直接改个类名不是更方便吗?没错,还有一种减少重排的方法就是切换类名,而不是使用内联样式的cssText方法。
切换类名就变成了这样:

// css
	.active {
		width:100px;
		height:200px;
		background:red;
}
//js
var div = document.querySelector('.div');
div.className = 'active';

这种优化方法只适用于单个存在的节点。
2. 批量修改DOM

实际工作中,我们看到的页面大多数都不是写死的,这样方便后期的维护。所以我们肯定会涉及到DOM的修改。假如我们现在需要动态创建10个节点,这个时候批量修改DOM的优化方式显得尤为重要。

批量修改DOM元素的核心思想:

  • 让该元素脱离文档流
  • 修改元素
  • 统一带回文档中

这个过程引发两次重排,第一步和第三步。如果我们每创建一个节点,就添加到页面中,进行一次计算。这样会把浏览器给累死(开个玩笑)。
注意:我们这里说的脱离文档流不是指css中使用浮动和决定定位,和这个没有关系

话不多说直接上代码:

// html
<ul class='imgList'>
	<li><img src='1.jpg'></li>
	<li><img src='2.jpg'></li>
</ul>

// js    添加如下信息
let data = [
	{ src : '3.jpg'},
	{ src : '4.jpg'},
	{ src : '5.jpg'},
	{ src : '6.jpg'},
	{ src : '7.jpg'},
	{ src : '8.jpg'},
]

写一个通用方法加载指定的数据:

function appendNode(node ,data) {
	var img , li;
	for(let i = 0 ; i< data.length; i++){
		var li = document.createElement('li');
		var img = document.createElement('img');
		img.src = data[i].src;
		li.appendChild(a);
		node.appendChild(li);
	}
}

在没有考虑性能的前提下,我们可能会这么写:

let ul = document.querySelector('.imgList');
appendNode(ul,data);

很显然,每创建一个li 和 img 就会添加到页面中。这样会造成多次回流。如果需要加载成百上千个li,必然会造成页面的卡顿。所以我们需要优化。

1)隐藏元素,进行修改后,然后再显示该元素

let ul = document.querySelector('.imgList');
ul.style.display = 'none';
appendNode(ul,data);
ul.style.display = 'block';

将操作的节点隐藏,发生一次重排,最后让节点显示发生一次重排。这种方法只发生了两次重排。相比于上面的方法,性能得到提升。
当元素样式为 **display:‘none’**的时候,元素脱离文档流。重新计算元素属性和重新构建渲染树,发生重排。
2) 使用文档碎片,将我们需要的创建的节点,再文档碎片中创建,然后统一的添加到页面节点中

let frg = document.createDocumentFragment();
appendNode(frg,data);
ul.appendChild(frg);

这个方法很好理解,打个比方:你需要扔垃圾,但是你总不能有一个点垃圾就下楼去大的垃圾桶。所以你买了一个自己的垃圾桶。当垃圾存满是你就一次性的下楼去丢掉。注意:每次丢完垃圾,但是垃圾桶还是会带上楼

为什么我要强调这一点呢?文档碎片是一个轻量级的document对象。它是用来更新,移动节点的。文档碎片有一个好处就是,当向一个节点添加文档片段时候,添加的是文档片段的子节点群,自身不会被添加进去。

3) 将原始元素拷贝到一个独立的节点中,操作这个节点,然后覆盖原始元素

let old = document.querySelector('.imgList');
let clone = old.cloneNode(true);
appendNode(clone,data);
old.parentNode.replaceChild(clone,old);

这种做法只发生了一次重排。

重排非常的消耗性能,我们能做的就是尽量的减小重排的次数。
总结:

1.样式:

  • 如果使用行内样式:el.style.cssText = ''
  • 直接切换类名
    2.批量修改DOM操作:
  • 节点隐藏
  • 文档碎片
  • 拷贝覆盖
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值