一、回流 Reflow & 重绘 Repaint
1、回流 Reflow
元素的大小或者位置发生了变化(当页面布局和几何信息发生变化的时候),触发了重新布局,导致渲染树重新计算布局和渲染
- 如添加或删除可见的DOM元素;
- 元素的位置发生变化;
- 元素的尺寸发生变化;
- 内容发生变化(比如文本变化或图片被另一个不同尺寸的图片所替代);
- 页面一开始渲染的时候(这个无法避免);
- 因为回流是根据视口的大小来计算元素的位置和大小的,所以浏览器的窗口尺寸变化也会引发回流……
第一次渲染完成后,基于JS改变了 元素的位置或者大小,浏览器需要重新计算渲染树中 每一个元素在视口中的 位置和大小
=> 这个阶段就是=> 重新布局 / 回流 / 重排 / reflow
2、重绘 Repaint
元素样式的改变(但宽高、大小、位置等不变)
- 如:outline, visibility, color, background-color……等
重绘不是很消耗性能
回流很消耗性能(DOM元素的大小和位置信息都要重新计算一遍),而且一旦发生回流,重新计算完后,还需要重绘
- 回流一定会触发重绘,而重绘不一定会回流
所以我们项目中要尽量减少操作DOM,在不可避免操作DOM时,我们也要尽量减少回流和重绘,来完成我们的性能优化;
- 第一次渲染页面的时候:触发一次回流和重绘(不可避免的)
二、避免DOM的回流
1、放弃传统操作 DOM 的时代,基于 vue/react 开始数据影响视图模式
- mvvm / mvc / virtual dom / dom diff ……
vue / react 数据驱动思想 :
我们自己不操作DOM,我们只操作数据,让框架帮我们根据数据渲染视图(框架内部本身对于DOM的回流和重绘以及其它性能优化做的非常好)
2、分离读写操作(现代浏览器的渲染队列的机制)(重要)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
.box {
width: 100px;
height: 100px;
background: red;
}
</style>
</head>
<body>
<div class="box" id="box"></div>
<ul id="item">
<!-- <li>我是第1个LI</li> -->
</ul>
</body>
</html>
复制代码
- 在老版本的浏览器中,我们分别改变了三次样式(都涉及了位置或者大小的改变),所以触发三次回流和重绘
// SCRIPT在DOM结构末尾导入,可以直接使用元素的ID代表这个元素对象
box.style.width = '200px';
box.style.height = '200px';
box.style.margin = '20px';
//=> 在老版本的浏览器中,我们分别改变了三次样式(都涉及了位置或者大小的改变),所以触发三次回流和重绘
复制代码
- 现代浏览器中默认增加了“渲染队列的机制”,以此来减少DOM的回流和重绘
=> 遇到一行修改样式的代码,先放到渲染队列中,继续看 下面一行代码 是否还为修改样式的,如果是继续增加到渲染队列中…直到下面的代码不再是修改样式的,而是获取样式的代码!此时不再向渲染队列中增加,把之前渲染队列中要修改的样式一次性渲染到页面中,引发一次DOM的回流和重绘
// 一次回流重绘
box.style.width = '200px';
box.style.height = '200px';
box.style.margin = '20px';
复制代码
所以我们利用这种机制来减少DOM的回流
// 三次回流和重绘
box.style.width = '200px';
console.log(box.style.width); //=>中断渲染队列,立即渲染一次,引发一次DOM回流和重绘 200px
box.style.height = '200px';
console.log(box.offsetHeight);
box.style.margin = '20px';
// 一次回流重绘
box.style.width = '200px';
box.style.height = '200px';
box.style.margin = '20px';
console.log(box.style.width);
console.log(box.offsetHeight);
复制代码
但是有的时候我们还需要浏览器多次回流:例如:先设置动画,渲染后,在去改变样式,让其有动画效果
//=> 此时动画并没有体现
box.style.transition = '.3s';
box.style.width = '200px';
box.style.height = '200px';
// 手动不分离读写的需求:先设置动画,渲染后,在去改变样式,让其有动画效果
box.style.transition = '.3s';
let AA = box.offsetHeight;
box.style.width = '200px';
box.style.height = '200px';
复制代码
3、样式集中改变(不重要)
- 通过修改样式类:把样式实现写好,我们后期通过样式类修改样式
- 用cssText的方式添加想要修改的样式
//=> 通过修改样式类:把样式实现写好,我们后期通过样式类修改样式
// 一次回流重绘
box.className = 'active';
//=> 把所有想写的样式,用cssText的方式添加
// 一次回流重绘
box.style.cssText = 'width:200px;height:200px;';
复制代码
4、缓存布局信息(不重要)
把要操作的内容一次都拿到,然后用变量存储,想设置的时候直接拿变量值即可,不用在重新获取了,和分离读写的原理类似
div.style.left = div.offsetLeft + 1 + 'px';
div.style.top = div.offsetTop + 1 + 'px';
=> 改为
var curLeft = div.offsetLeft;
var curTop = div.offsetTop;
div.style.left = curLeft + 1 + 'px';
div.style.top = curTop + 1 + 'px';
复制代码
5、元素批量修改(重要)
DOM的增加也会引起回流重绘
/* 在动态操作DOM结构中的优化(例如:数据绑定) */
for (let i = 1; i <= 5; i++) {
let liBox = document.createElement('li');
liBox.innerText = `我是第${i}个LI`;
item.appendChild(liBox);
//=>每一次向页面中增加,都会触发一次DOM的回流和重绘(5次)
}
复制代码
- 文档碎片:临时创建的一个存放文档的容器,我们可以把新创建的LI,存放到容器中,当所有的LI都存储完,我们统一把容器中的内容增加到页面中(只触发一次回流)
// 文档碎片:临时创建的一个存放文档的容器,我们可以把新创建的LI,存放到容器中,当所有的LI都存储完,我们统一把容器中的内容增加到页面中(只触发一次回流)
let frag = document.createDocumentFragment();
for (let i = 1; i <= 5; i++) {
let liBox = document.createElement('li');
liBox.innerText = `我是第${i}个LI`;
frag.appendChild(liBox);
}
item.appendChild(frag);
复制代码
- 字符串拼接:项目中,有一个文档碎片类似的方式,也是把要创建的LI事先存储好,最后统一放到页面中渲染(字符串拼接)
// 项目中,有一个文档碎片类似的方式,也是把要创建的LI事先存储好,最后统一放到页面中渲染(字符串拼接)
let str = ``;
for (let i = 1; i <= 5; i++) {
str += `<li>我是第${i}个LI</li>`;
}
item.innerHTML = str;
复制代码
6、动画效果应用到 position 属性为 absolute 或 fixed 的元素上(脱离文档流)
- 也会引起回流重绘,只不过从新计算过程中,因为他脱离文档流了,不会对其他元素产生影响,重新计算的过程中比较快一点
7、CSS硬件加速(GPU加速)
- 比起考虑如何减少回流重绘,我们更期望的是,根本不要回流重绘:transform/opacity/filters……这些属性会触发硬件加速,不会引发回流和重绘……
- 可能会引发坑:过多使用会占用大量内存,性能消耗严重,有时候会导致字体模糊等
8、牺牲平滑度换取速度
- 每次1像素移动一个动画,但是如果此动画使用了100%的CPU,动画就会看上去是跳动的,因为浏览器正在与回流做东征。每次移动3像素可能开起来平滑度低了,但它不会导致CPU在较慢的机器中抖动