前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >前端性能优化--卡顿心跳检测

前端性能优化--卡顿心跳检测

原创
作者头像
被删
发布2024-08-05 10:02:41
2810
发布2024-08-05 10:02:41
举报
文章被收录于专栏:被删的前端游乐场

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

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

requestAnimationFrame 心跳检测

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

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

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

代码语言:ts
复制
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()来取消刷新回调请求,因此我们可以基于此开实现启动和停止检测的能力:

代码语言:ts
复制
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基于页面的绘制来执行回调的,当我们页面被切走之后,显然不会触发回调,那么可能存在一个问题:此时检测的耗时很可能会超出卡顿阈值。

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

代码语言:ts
复制
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来看看怎么实现。

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

查看Github有更多内容噢: https://github.com/godbasin

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • requestAnimationFrame 心跳检测
    • 启动和停止检测
      • 页面隐藏
      • 结束语
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档