为什么使用setTimeout计算fps不准确?

问题描述 投票:0回答:1

我的想法

  1. 递归调用setTimeout(1000 / 60),每次调用fps加一
  2. 每当通话时间超过1秒,记录fps
  3. 页面不可见时清除计时器,页面可见时启动计时器。

但是我录制的fps有时会超过70,为什么?

下面是我的伪代码

const INITIAL_FPS = 0;

export const useReportFPS = () => {
  const lastRecordTime = useRef(Date.now());
  const fps = useRef(INITIAL_FPS);
  const loopShouldRun = useRef(false);
  const timerId = useRef<NodeJS.Timer | null>(null);

  const clearTimer = useCallback(() => {
    if (timerId.current) {
      clearTimeout(timerId.current);
      timerId.current = null;
    }
  }, []);

  const fragmentation = useRef((callback: () => void) => {
    // 同一时刻内存中只有一个 fps 定时器
    clearTimer();
    timerId.current = setTimeout(() => {
      callback();
      timerId.current = null;
    }, 1000 / 60);
  });

  const handleFps = useCallback((cost: number, reportFPS: number) => {
    /** cost 时间段内经历了几个整数秒 */
    const count = Math.floor(cost / 1000);
    // 正常情况下,两次 setTimeout 调用间隔小于 2 s 上报一次 fps
    console.log('fps', reportFPS);
    // 异常情况下,两次 setTimeout 调用间隔大于 N + 1 s,后 N s 的 fps 认为是 0
    for (let i = 1; i < count; i++) {
      console.log('fps', reportFPS);
    }
  }, []);
  /** 递归调用计算和上报 fps */
  const recursiveCollectFPS = useCallback(() => {
    fragmentation.current(() => {
      // 页面不可见或标识位为 false 时停止递归调用
      if (loopShouldRun.current === false || document.hidden) {
        return;
      }
      fps.current += 1;
      const current = Date.now();
      const cost = current - lastRecordTime.current;
      if (cost > 1000) {
        handleFps(cost, fps.current);
        lastRecordTime.current = current;
        fps.current = INITIAL_FPS;
      }
      recursiveCollectFPS();
    });
  }, []);

  const startCollectFPS = useCallback(() => {
    loopShouldRun.current = true;
    fps.current = INITIAL_FPS;
    lastRecordTime.current = Date.now();
    recursiveCollectFPS();
  }, []);

  const onVisibilitychange = useCallback(() => {
    if (document.visibilityState === 'visible') {
      // 页面由不可见 -> 可见时,重新开始计算 fps
      startCollectFPS();
    } else {
      clearTimer();
    }
  }, []);

  const stopCollectFPS = useCallback(() => {
    loopShouldRun.current = false;
    clearTimer();
    document.removeEventListener('visibilitychange', onVisibilitychange);
  }, []);

  const init = useCallback(() => {
    document.addEventListener('visibilitychange', onVisibilitychange);
    startCollectFPS();
  }, []);

  useEffect(() => {
    init();
    return () => {
      stopCollectFPS();
    };
  }, []);
};

预计: fps 是这样计算的 <= 64

javascript settimeout frame-rate requestanimationframe
1个回答
0
投票

有多种原因导致超时可能需要比预期更长的时间

嵌套超时

根据 HTML 标准中的规定,一旦对 setTimeout 的嵌套调用被安排 5 次,浏览器将强制执行 4 毫秒的最小超时。

非活动选项卡超时

为了减少后台选项卡的负载(以及相关的电池使用量),浏览器将在非活动选项卡中强制执行最小超时延迟。

跟踪脚本的限制

Firefox 对它识别为跟踪脚本的脚本强制执行额外的限制。前台运行时,节流最小延迟仍为4ms。然而,在后台选项卡中,限制最小延迟为 10,000 毫秒,即 10 秒,在文档首次加载后 30 秒生效。

请参阅跟踪保护了解更多详情。

迟到的超时

如果页面(或操作系统/浏览器)正忙于其他任务,超时也可能比预期晚触发。需要注意的一个重要情况是,在调用 setTimeout() 的线程终止之前,函数或代码片段无法执行。

function foo() {
  console.log("foo has been called");
}
setTimeout(foo, 0);
console.log("After setTimeout");

© www.soinside.com 2019 - 2024. All rights reserved.