前端性能优化-回流与重绘

前言

本文总结回流与重绘相关的知识点

回流与重绘的基本概念

重绘(Repaint): 当元素样式发生改变,但不影响其几何属性的时候,浏览器只需要重新绘制这个元素,这个过程被称为重绘。

回流(Reflow): 当页面布局和几何属性发生变化,导致部分或全部元素的尺寸、位置、结构发生改变时,浏览器需要重新计算元素的几何属性和页面的布局,这个过程被称为回流。

重绘不一定会触发回流,回流一定会触发重绘。

触发条件

回流和重绘的触发条件主要包括对 DOM 结构和样式的修改。以下是触发回流和重绘的常见操作:

回流的触发条件:

  • 初始化页面。
  • 改变窗口大小。
  • 添加、删除、更新 DOM 元素。
  • 改变元素的位置、尺寸、内容。
  • 修改元素的 font、padding、margin 等样式属性。

对于回流与重绘,我们可以直观地通过 chrome 的开发者工具来查看:

比如下面的 js 操作

document.querySelector(".box").addEventListener("click", function () {
  // 改变元素尺寸
  this.style.width = "200px";
});

通过 chrome 控制面板可以看到:

完整的 demo 请看👉 在线效果预览, 查看示例代码请点击此处

网上有说法: 获取 offsetTop、offsetLeft、offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth.
scrollHeight、clientTop、clientLeft、clientWidth、clientHeight 以及调用 getComputedStyle 等属性和方法都会触发回流。
但是从 chrome 控制面板中看到这些现象并不会触发回流,所以这些说法有待商榷。

重绘的触发条件:

  • 修改元素的颜色、背景色。
  • 修改元素的 visibility、outline、box-shadow 等不影响布局的属性。

比如下面的 js 操作

document.querySelector(".box").addEventListener("click", function () {
  // 修改元素颜色
  this.style.backgroundColor = "red";
});

完整的 demo 请看👉 在线效果预览, 查看示例代码请点击此处

仔细与上图对比我们可以发现少了Layout 这一步,说明只触发了重绘,没有触发回流。

优化策略与最佳实践

合并多次修改

合并多次 DOM 和样式修改的优势,减少回流和重绘的次数。

// 不优化的写法,会触发多次回流和重绘
element.style.width = "100px";
element.style.height = "100px";
element.style.backgroundColor = "red";

// 优化的写法,合并为一次回流和重绘
element.style.cssText = "width: 100px; height: 100px; background-color: red;";

使用 class 操作

通过修改元素的类而不是直接操作样式,可以利用浏览器对类的处理进行优化。这样可以减少直接样式修改导致的回流和重绘。

// 不优化的写法,会触发回流和重绘
element.style.width = "100px";
element.style.height = "100px";
element.style.backgroundColor = "red";

// 优化的写法,通过修改类名实现
element.classList.add("custom-style");
/* CSS中定义样式 */
.custom-style {
  width: 100px;
  height: 100px;
  background-color: red;
}

离线操作

执行离线 DOM 操作,即在修改大量 DOM 元素之前,先将它们从文档中移除,进行操作后再放回文档。这样可以最小化对页面渲染的直接影响,降低回流和重绘的次数。

// 不优化的写法,会触发多次回流和重绘
for (let i = 0; i < 100; i++) {
  document.body.appendChild(document.createElement("div"));
}

// 优化的写法,离线操作
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
  const div = document.createElement("div");
  fragment.appendChild(div);
}
document.body.appendChild(fragment);

浏览器中的 Flush 队列

在浏览器的渲染引擎中,当发生 DOM 或样式变化时,引擎并不会立即执行相应的回流和重绘操作。相反,它将这些操作放入一个队列中,这个队列被称为 Flush 队列

具体的合并机制可以归结为以下几点:

  • 批量执行: 浏览器会等待一定时间,收集多个回流和重绘任务,然后一次性执行它们。这样可以减少任务的切换开销,并优化性能。
  • 策略性合并: 浏览器会根据一些策略性的考虑,将一些相关联的回流和重绘任务合并在一起。例如,如果某个元素的多个样式同时发生变化,浏览器可能会将它们合并成一个操作,以减少回流和重绘的次数。
  • 异步执行: 部分回流和重绘任务可能会被推迟到下一个事件循环(Event Loop)中执行。这意味着在当前事件循环中发生的多个 DOM 和样式变化可能会在下一个事件循环中被合并执行,从而减少了当前事件循环中的任务执行开销。

以下面一段 js 代码为例

const box = document.querySelector(".box");

box.addEventListener("click", () => {
  // 多次修改样式,合并为一次回流和重绘
  function performMultipleChanges() {
    box.style.width = "200px";
    box.style.height = "200px";
    box.style.backgroundColor = "red";
  }

  // 调用多次修改,观察合并效果
  performMultipleChanges();
  performMultipleChanges();
  performMultipleChanges();
});

执行效果如下图所示,可以看到chrome对上述的多个style变更以及多次的performMultipleChanges函数调用进行了合并,最终只触发了一次回流和重绘。

值得注意的是,并不是所有的浏览器都做了这些优化的操作,因此作为开发者,还是要提高自己的知识储备,尽量避免不必要的回流和重绘。

本文首发于个人博客前端开发笔记,由于笔者能力有限,文章难免有疏漏之处,欢迎指正

  • 7
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值