概念理解
回流(Reflow):当元素的布局属性(如宽度、高度、位置等)发生变化时,浏览器需要重新计算元素的几何属性,并重新布局页面,这个过程称为回流(页面首次渲染时叫做 Layout 布局)。
回流影响范围:
- 全局范围:从根节点开始,对整个渲染树进行重新布局。
- 局部范围:对渲染树的某部分或者一个渲染对象进行重新布局。
重绘(Repaint):当元素的外观属性(如颜色、背景、边框等)发生变化,但不影响布局时,浏览器只需要重新绘制元素,这个过程称为重绘。
记住:当触发回流时,一定会触发重绘,但是重绘不一定会触发回流。
触发回流的情况:
- 页面的首次渲染。
- 浏览器的窗口大小发生变化(滚动条)。
- 元素的内容发生变化。
- 元素的尺寸或者位置发生变化:宽高、边框、切换定位、浮动、display。
- 元素的字体大小发生变化:
font-size、font-family、white-space。 - 查询某些属性或者调用某些 API:
offset/client/scrollXXX、getComputedStyle,滚动scrollTo()。 - 添加或者删除可见的 DOM 元素:
appendChild、insertBefore、removeChild。
只触发重绘不会触发回流的属性:
border-radius:边框圆角。visibility:元素可见性。box-shadow:盒子阴影。color:文字颜色。background-color:背景颜色。outline:轮廓。opacity:透明度(在合成层中)。
减少回流与重绘的措施
1. DOM 操作优化
- 尽量在低层级的 DOM 节点进行操作:减少影响范围,避免触发大范围的回流。
- 不要使用 table 布局:可能会使整个 table 进行重新布局。
- 修改类名:而不是直接修改样式,通过类名切换批量修改样式。
- 使用 absolute 或者 fixed:使元素脱离文档流,这样它们发生变化就不会影响其他元素。
- 将元素先设置
display: none:操作结束后再把它显示出来。此时元素 DOM 操作不会引发回流和重绘。 - 避免 CSS 表达式 :CSS 表达式(如
calc())会被频繁计算,导致性能问题,应避免使用。
2. 文档片段 DocumentFragment
DocumentFragment 文档片段接口,是没有父对象的最小文档对象,和正常的文档节点差不多,但在上面添加和删除节点,不会触发真实 DOM 树的重新渲染。
当 DocumentFragment 插入到文档中时,会一次性插入它所有的子孙节点,不会和直接操作 DOM 树一样引起频繁的重排和重绘。
javascript
const fragment = document.createDocumentFragment();
// 应用所有 DOM 操作后再插入文档
for (let i = 0; i < 1000; i++) {
const li = document.createElement("li");
li.textContent = `Item ${i}`;
fragment.appendChild(li);
}
document.getElementById("list").appendChild(fragment);
3. 渲染队列机制
将 DOM 的多个读操作或写操作放在一起,避免读写操作穿插着写。
浏览器存在渲染队列机制,执行写操作会先放一起累计,等到一定程度/触发读时再执行重排重绘。
javascript
// ❌ 读写操作穿插
const width = element.offsetWidth; // 触发回流
element.style.width = width + 10 + "px"; // 写操作
const height = element.offsetHeight; // 触发回流
element.style.height = height + 10 + "px"; // 写操作
// ✅ 批量写操作
element.style.width = element.offsetWidth + 10 + "px";
element.style.height = element.offsetHeight + 10 + "px";
动画优化
1. 使用定位属性脱离文档流
使用 absolute 和 fixed 脱离文档流,使动画元素不影响其他元素的布局。
2. 使用 transform 和 opacity
使用 transform 和 opacity 进行动画,这些属性可以在合成层中由 GPU 处理。
浏览器会将使用这些属性的元素提升为合成层,在合成器线程中直接处理,具体触发渲染位置查看 浏览器帧渲染流程理解,无需重新计算布局和绘制,无需参与主线程的布局和重绘流程。
优势:
transform:在合成层中处理,不会触发回流和重绘。opacity:在合成层中处理,不会触发回流和重绘。
注意 :使用 transform3D 可以提升 GPU 合成层完成动画操作,但不宜过多合成层,会占用较大内存。
示例:
css
/* ❌ 可能使用会触发回流的属性 */
.element {
animation: move 1s;
}
@keyframes move {
from {
left: 0;
}
to {
left: 100px;
}
}
/* ✅ 使用 transform 3D */
.element {
animation: move 1s;
}
@keyframes move {
from {
transform: translateX(0);
}
to {
transform: translateX(100px);
}
}
3. 使用 will-change 提示浏览器
使用 will-change 属性提前告知浏览器元素将要发生变化,浏览器可以提前优化。
注意 :不过度使用 will-change,因为会创建新的合成层,占用内存。
css
.element {
will-change: transform;
}
其他
- 仅渲染屏幕内容 :减少屏幕绘制内容,也可以达到优化效果。
- 虚拟滚动:使用虚拟滚动插件,虚拟滚动本质是通过创建一个超大或符合展示所有数据的容器,用于模拟滚动效果,然后计算数据项的索引值,每次只渲染界面中出现的内容。
- Intersection Observer:监听内容是否在屏幕内,是则渲染,不是则保留布局容器,其余隐藏,减少布局渲染压力。在渲染大量彩色文本内容,需要嵌套多层元素的时候非常有用。
content-visibility:auto:CSS 属性,可以实现离屏降低渲染压力的效果,搭配contain-intrinsic-size可以避免滚动条塌陷,具体参考 content-visibility。
总结
-
核心概念:回流会重新计算布局,重绘只重新绘制外观。回流一定会触发重绘,但重绘不一定触发回流。
-
优化策略:
- DOM 操作优化:在低层级节点操作、使用类名切换、脱离文档流、批量操作。
- 使用 DocumentFragment:批量 DOM 操作时使用,减少回流和重绘次数。
- 渲染队列机制:批量读写操作,避免读写穿插。
- 动画优化 :使用
transform和opacity进行动画,利用 GPU 合成层。
-
注意事项:
- 避免使用 table 布局和 CSS 表达式。
- 合理使用
will-change,避免创建过多合成层。 - 优先使用只触发重绘的属性,避免触发回流。