文章目录
  1. 1. requestAnimationFrame 心跳检测
    1. 1.1. 启动和停止检测
    2. 1.2. 页面隐藏
  2. 2. 结束语

对于重前端计算的网页来说,性能问题天天都冒出来,而操作卡顿可能会直接劝退用户。

前面我们在《前端性能优化–卡顿的监控和定位》一文中介绍过一些卡顿的检测方案,这里我们来讲一下具体的代码实现逻辑好了。

requestAnimationFrame 心跳检测

这里我们使用window.requestAnimationFrame来作为检测卡顿的核心机制。

前面也有说过,requestAnimationFrame()会在浏览器下次重绘之前调用,60Hz 的电脑显示器每秒钟requestAnimationFrame会被执行 60 次。

那么,我们可以简单地判断,假设两次requestAnimationFrame之间的执行耗时超过一定值,则可以认为浏览器的重绘被阻塞了,页面响应产生了卡顿,这里我们将该值设置为 1s:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class HeartbeatMonitor {
// 上一次心跳的时间
private preHeartBeatTime: number;

private checkNextTick() {
this.preHeartBeatTime = Date.now();
requestAnimationFrame(() => {
const currentTime = Date.now();
// 取出执行耗时
let timeDistance = currentTime - this.preHeartBeatTime;
// 超过 1s 则认为是卡顿了
if (timeDistance > 1000) {
// 注:dispatchEvent 为伪代码,具体可自行实现
// 对外抛事件表示发生了卡顿
this.dispatchEvent('jank');
} else {
// 对外抛事件表示为普通心跳
this.dispatchEvent('heartbeat');
}
// 继续下一次检测
this.checkNextTick();
});
}
}

通过这种方式,我们简单判断代码执行是否产生了卡顿。当然,我们在实际使用的时候,还需要提供开启和停止检测的能力:

启动和停止检测

已知requestAnimationFrame的返回值是一个请求 ID,用于唯一标识回调列表中的条目,可以使用window.cancelAnimationFrame()来取消刷新回调请求,因此我们可以基于此开实现启动和停止检测的能力:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class HeartbeatMonitor {
// 上一次心跳的时间
private preHeartBeatTime: number;
// 心跳定时器
private heartBeatTimer: number | null = null;

/**
* 开启卡顿监控
*/
start() {
if (!this.heartBeatTimer) this.checkNextTick();
}

/**
* 结束卡顿监控
*/
stop() {
// 取消 requestAnimationFrame
if (this.heartBeatTimer) cancelAnimationFrame(this.heartBeatTimer);
this.heartBeatTimer = null;
}

private checkNextTick() {
this.preHeartBeatTime = Date.now();
this.heartBeatTimer = requestAnimationFrame(() => {
const currentTime = Date.now();
// 取出执行耗时
let timeDistance = currentTime - this.preHeartBeatTime;
// 超过 1s 则认为是卡顿了
if (timeDistance > 1000) {
// 注:dispatchEvent 为伪代码,具体可自行实现
// 对外抛事件表示发生了卡顿
this.dispatchEvent('jank');
} else {
// 对外抛事件表示为普通心跳
this.dispatchEvent('heartbeat');
}
// 继续下一次检测
this.checkNextTick();
});
}
}

当然,对于有状态的运行期,最好我们还可以给其加上一个状态位标志,来避免重复调用、外界获取状态等情况,不过这个很简单,大家可以自行实现。

页面隐藏

由于requestAnimationFrame基于页面的绘制来执行回调的,当我们页面被切走之后,显然不会触发回调,那么可能存在一个问题:此时检测的耗时很可能会超出卡顿阈值。

因此,我们还需要对页面是否被切走的场景做处理,最简单莫过于页面切走之后就停止,切回来再打开:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class HeartbeatMonitor {
// 上一次心跳的时间
private preHeartBeatTime: number;
// 心跳定时器
private heartBeatTimer: number | null = null;

constructor() {
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === "hidden") {
this.stop();
} else {
this.start();
}
});
}

/**
* 开启卡顿监控
*/
start() {
if (!this.heartBeatTimer) this.checkNextTick();
}

/**
* 结束卡顿监控
*/
stop() {
// 取消 requestAnimationFrame
if (this.heartBeatTimer) cancelAnimationFrame(this.heartBeatTimer);
this.heartBeatTimer = null;
}

private checkNextTick() {
this.preHeartBeatTime = Date.now();
this.heartBeatTimer = requestAnimationFrame(() => {
const currentTime = Date.now();
// 取出执行耗时
let timeDistance = currentTime - this.preHeartBeatTime;
// 超过 1s 则认为是卡顿了
if (timeDistance > 1000) {
// 注:dispatchEvent 为伪代码,具体可自行实现
// 对外抛事件表示发生了卡顿
this.dispatchEvent('jank');
} else {
// 对外抛事件表示为普通心跳
this.dispatchEvent('heartbeat');
}
// 继续下一次检测
this.checkNextTick();
});
}
}

结束语

现在我们实现了卡顿的检测,但是基于此我们只能得到页面在运行过程中是否产生了卡顿,但是难以定位卡顿的问题出现在哪。前面《前端性能优化–卡顿的监控和定位》一文中有大致介绍堆栈的方法,我们下一篇来说一下基于当前的HeartbeatMonitor来看看怎么实现。

主要是分两篇来讲的话,我就可以偷个懒啦:)

码生艰难,写文不易,给我家猪囤点猫粮了喵~

B站: 被删

查看Github有更多内容噢:https://github.com/godbasin
更欢迎来被删的前端游乐场边撸猫边学前端噢

如果你想要关注日常生活中的我,欢迎关注“牧羊的猪”公众号噢

作者:被删

出处:https://godbasin.github.io

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

文章目录
  1. 1. requestAnimationFrame 心跳检测
    1. 1.1. 启动和停止检测
    2. 1.2. 页面隐藏
  2. 2. 结束语