my thoughts
- Call setTimeout(1000 / 60) recursively, fps increases by one each time it is called
- Whenever the call time exceeds 1s, record fps
- Clear the timer when the page is not visible, and start the timer when the page is visible.
But my recorded fps sometimes exceeds 70, why?
Below is my pseudo code
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();
};
}, []);
};
expected: fps calculated this way <= 64