先了解一下浏览器的渲染机制
以上两张图来自MDN,我们可以看出,浏览器的渲染工作过程如下:
- 浏览器解析HTML,生成DOM树;解析CSS,生成CSSOM树;
- 将DOM树和CSSOM树结合,生成渲染树(Render Tree);
- Layout阶段:根据生成的渲染树,进行Layout,得到节点的几何信息(位置,大小);
- Painting阶段:根据渲染树以及回流得到的几何信息,得到节点的绝对像素;
- Display:将像素发送给GPU,展示在页面上。
我们记住浏览器渲染页面的这五个主要流程,下面会用到。
回流or重排 (Reflow)
元素的大小或者位置发生了变化(当页面布局和几何信息发生变化的时候),触发了重新布局,导致渲染树重新计算布局和渲染,会把上面浏览器渲染1-5流程重新走一遍,这个过程称为:回流(Reflow)。
由此也得到,频繁触发浏览器的回流,对性能损耗较大。
因为现在的浏览器都是使用流式布局模型 (Flow Based Layout),一旦文档流中某个元素发生形状改变,势必牵一发而动全身。
会导致回流的操作:
- 页面首次渲染
- 浏览器窗口大小发生改变
- 元素尺寸或位置发生改变
- 元素内容变化(文字数量或图片大小等等)
- 元素字体大小变化
- 添加或者删除可见的DOM元素
- 激活CSS伪类(例如::hover)
- 查询某些属性或调用某些方法
一些常用且会导致回流的属性和方法:
clientWidth、clientHeight、clientTop、clientLeft offsetWidth、offsetHeight、offsetTop、offsetLeft scrollWidth、scrollHeight、scrollTop、scrollLeft scrollIntoView()、scrollIntoViewIfNeeded() getComputedStyle() getBoundingClientRect() scrollTo()
重绘 (Repaint)
元素样式的改变(但宽高、大小、位置等不变),浏览器根据新样式属性进行对应更新,这个过程叫重绘(Repaint)。
如:outline, visibility, color, background-color......等。
对比回流,重绘对浏览器性能消耗较小。
这里也能明白,回流一定重绘,重绘不一定回流。
减少DOM的回流和重绘
1、感谢现代浏览器的渲染队列机制
可以先来看下这样一道题,问触发几次回流重绘:
来自于掘友:阳光是sunny
答案如掘金用户阳光是sunny所说:古董浏览器需要两次,现代浏览器只要一次即可。
现代浏览器中默认增加了“渲染队列的机制”,以此来减少DOM的回流和重绘。其工作原理如下:
遇到一行修改样式的代码,先放到渲染队列中,继续看下面一行代码是否还为修改样式的,如果是继续增加到渲染队列中...直到下面的代码不再是修改样式的,而是获取样式的代码!此时不再向渲染队列中增加,把之前渲染队列中要修改的样式一次性渲染到页面中,引发一次DOM的回流和重绘。
2、放弃传统操作 DOM 的时代,基于 vue/react 开始数据影响视图模式
我们自己不操作DOM,我们只操作数据,让框架帮我们根据数据渲染视图(框架内部本身对于DOM的回流和重绘以及其它性能优化做的非常好
3、批量修改DOM
比如,像页面中的ul元素中动态添加li:
for (let i = 1; i <= 100; i++) {
let liBox = document.createElement('li');
liBox.innerText = `我是第${i}个LI`;
ul.appendChild(liBox);
//=>每一次向页面中增加,都会触发一次DOM的回流和重绘(100次)
}
为了减少不必要的回流和重绘,可以采取以下方式:
- 字符串拼接方式 :先把要更新的DOM拼接成字符串,最后一次性统一渲染。
let str = ``;
for (let i = 1; i <= 100; i++) {
str += `<li>我是第${i}个LI</li>`;
}
ul.innerHTML = str;
- 文档碎片方式:临时创建的一个存放文档的容器,我们可以把新创建的元素,存放到容器中,当所有的元素都存储完,我们统一把容器中的内容增加到页面中(只触发一次回流)
let frag = document.createDocumentFragment();
for (let i = 1; i <= 100; i++) {
let liBox = document.createElement('li');
liBox.innerText = `我是第${i}个LI`;
frag.appendChild(liBox);
}
ul.appendChild(frag);
4、动画效果应用到 position 属性为 absolute 或 fixed 的元素上(脱离文档流)
也会引起回流重绘,只不过从新计算过程中,因为他脱离文档流了,不会对其他元素产生影响,重新计算的过程中比较快一点
5、CSS方面补充
- 避免使用table布局;
- 避免使用CSS表达式(例如:calc())
- CSS硬件加速(GPU加速)