用requestAnimationFrame+performance.now()计算FPS:记录每帧时间差,每秒统计帧数并取整,避免单帧抖动;需监听visibilitychange确保页面隐藏时暂停统计。
直接在页面上显示实时 FPS 数值,用 requestAnimationFrame + performance.now() 就够了,不需要第三方库,也不必动用 WebGL 或 video API。
requestAnimationFrame 算出当前 FPS核心是记录两帧之间的时间差,再换算成每秒帧数。浏览器不提供“当前 FPS”变量,得自己算:
performance.now() 返回高精度时间戳(毫秒,带小数),比 Date.now() 准得多requestAnimationFrame 回调里计算与上一帧的时间差 delta
Math.round(1000 / delta),但注意:单帧抖动大时这个值会剧烈跳变,不适合直接展示frameCount 累计该秒内执行了多少次 rAF示例代码片段(可直接塞进 <script>):
let lastTime = performance.now();let frameCount = 0;let fps = 0;<p>function updateFPS() {const now = performance.now();frameCount++;</p><p>if (now - lastTime >= 1000) {fps = frameCount;frameCount = 0;lastTime = now;}}</p><p>function render() {updateFPS();document.getElementById('fps').textContent = <code>FPS: ${fps}</code>;requestAnimationFrame(render);}render();
setInterval(fn, 16) 模拟 60FPS它看起来简单,但实际会破坏帧同步,带来三类问题:
立即学习“前端免费学习笔记(深入)”;
setInterval 会积压回调,切回前台瞬间狂刷一堆帧,造成“突增—卡顿”循环requestAnimationFrame 是浏览器原生调度的,自动适配刷新率、后台降频、设备节电策略,setInterval 在这方面完全不可控。
如果你在 Canvas 渲染循环里同时做计算、绘制、更新 FPS,单帧耗时可能超过 16.67ms,导致 rAF 实际掉帧,而你显示的 FPS 却还是“60”——那是错觉。
updateFPS() 只做计时和计数,别在里面塞 drawImage、getImageData 这类重操作ctx.drawImage() 或 ctx.putImageData() 调用开销大,可加帧率限制开关,例如用 if (frameCount % 3 === 0) 控制每 3 帧处理一次像素要停。不是因为“省电”这种泛泛的理由,而是行为一致性问题:
document.visibilityState === 'hidden' 时,rAF 会被浏览器暂停或大幅降频(如降到 1–2FPS),此时继续用 performance.now() 算出的“FPS”毫无意义lastTime 可能是 30 秒前的值,1000 / delta 直接算出 0 或 NaNvisibilitychange 事件,在隐藏时清空计数器、暂停 rAF;恢复时重置 lastTime 并重启循环最容易被忽略的一点:FPS 显示本身是 UI 反馈,不是性能指标源。它只是帮助你判断“当前动画是否卡”,而不是替代 Performance 面板里的帧分析。真要定位卡顿,还得看 Chrome DevTools 的 “Performance” 录制结果,特别是主线程阻塞时长和 Raster 线程占用率。