之前分享过不少关于前端卡顿检测的内容,实际上在前端应用里,FPS 也是我们常用的一个页面流畅度的指标。
除了前面介绍的卡顿检测之外,我们还可以使用 FPS 来辅助定义用户体验。
前端页面 FPS
我们都知道,帧率(英语:frame rate)是用于测量显示帧数的度量。
FPS 则是帧率的测量单位,为“每秒显示帧数”(frame per second,FPS)或“赫兹”,一般来说FPS用于描述影片、电子绘图或游戏每秒播放多少帧。
前面《前端性能优化–任务管理和调度》一文介绍过,浏览器的“一帧”流程为:

- 用户事件。
- 一个宏任务。
- 队列中全部微任务。
requestAnimationFrame
。
- 浏览器重排/重绘。
requestIdleCallback
。
因此,在前端开发中,我们常常会使用requestAnimationFrame
来计算前端页面的 FPS。
每秒帧数计算 FPS
既然知道浏览器中每“一帧”里都会执行requestAnimationFrame
,那么 FPS 的计算也很简单:计算一秒内requestAnimationFrame
的执行次数 n,则 FPS 为 n。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| let frameCount = 0; let lastTime = performance.now(); function loop () { let now = performance.now(); frameCount++; if (now > lastTime + 1000) { let fps = Math.round((frameCount * 1000) / (now - lastTime)); console.log(`${now} 1S 内 FPS:`, fps); frameCount = 0; lastTime = now; }; window.requestAnimationFrame(loop); } loop();
|
通过这种方式,我们可以获取每秒平均的 FPS,但对于 1s 内的一些卡顿可能检测的不算灵敏,这种情况我们可以考虑用另外一种计算方式。
rAF 间隔计算 FPS
除了计算每秒的渲染次数,我们还有另外一种计算方式:假设两次requestAnimationFrame
间的执行间隔为 t 毫秒,那么 FPS 可以为 1000 / t。
这种方式的好处是,我们可以更准确获取到整个监测过程中的页面流畅度。
1 2 3 4 5 6 7 8 9 10 11 12
| let lastTime = performance.now(); function loop () { let now = performance.now(); let fps = Math.round(1000 / (now - lastTime)); console.log(`${now} FPS:`, fps); window.requestAnimationFrame(loop); } loop();
|
一般来说,由于requestAnimationFrame
的执行次数实在太多了,如果逻辑太多则反而会导致性能问题。
因此,很多时候我们并不会在整个页面打开过程中都启动 FPS 检测,会在某些场景下才启动 FPS 监测。
FPS 监测模块
由于用户并不是时时刻刻都在操作页面,不进行操作时,页面的流畅度便有些无从谈起。
所以,我们可以局部监测用户行为和页面行为,在需要的时候启动 FPS 监控:
为此,我们需要提供一个 FPS 监测模块,包括启动和结束统计某阶段 FPS 的能力:
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
| class Fps { private running = false; private last = 0; private timer = 0; private record: number[] = [];
public start = () => { if (this.running) { console.warn('已经启动 fps 监听'); return; } this.running = true; this.last = performance.now(); this.timer = requestAnimationFrame(this.run); };
public end = () => { window.cancelAnimationFrame(this.timer); this.running = false; if (this.record.length > 0) { this.calculateFPS(); this.record.length = 0; } };
private run = () => { const now = performance.now(); const diff = now - this.last; this.record.push(diff); this.last = now; this.timer = requestAnimationFrame(this.run); };
private calculateFPS = () => { const record = this.record; const avageFPS = record.reduce((a, b) => (a + b), 0) / record.length;
console.log(`该阶段平均 FPS:${avageFPS}`); }; }
|
前面也说过,我们希望更准确获取到整个监测过程中的页面流畅度,而不只是一个简单的平均值。因此,我们可以统计该阶段中 FPS 整体情况:
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
| export function getIntervalFPS(recore: number[]) { const internalMap = { '0-10': 0, '11-20': 0, '21-30': 0, '31-40': 0, '41-50': 0, '51-60': 0, '>60': 0, }; let maxFps = 0;
recore.forEach((interval) => { const value = Math.round(1000 / interval); maxFps = Math.max(value, maxFps); switch (true) { case value <= 10: internalMap['0-10'] += 1; break; case value <= 20: internalMap['11-20'] += 1; break; case value <= 30: internalMap['21-30'] += 1; break; case value <= 40: internalMap['31-40'] += 1; break; case value <= 50: internalMap['41-50'] += 1; break; case value <= 60: internalMap['51-60'] += 1; break; default: internalMap['>60'] += 1; break; } });
return { internalMap, maxFps, }; }
|
通过这样的方式,我们可以获得某个监控阶段期间,是否存在交互不流畅的情况,而不仅是一个总的平均值。
结束语
FPS 检测和卡顿检测其实原理都很相似,卡顿可能会监测整个页面生命周期,而 FPS 则用来监控页面滚动、用户交互、页面渲染等流程。
两者相辅相成,可以从不同的维度共同搭建前端页面的流畅度。
查看Github有更多内容噢:https://github.com/godbasin
更欢迎来被删的前端游乐场边撸猫边学前端噢
如果你想要关注日常生活中的我,欢迎关注“牧羊的猪”公众号噢