使用requestAnimationFrame与Date.now()时出现delta值不一致问题
我编写了一段简单代码:
let startTime = -1 let lastNow = -1 let lastTS = -1 const animate = (timeStamp)=>{ requestAnimationFrame(animate) const now = Date.now() if(lastTS===-1){ lastTS = timeStamp lastDate = now } const deltaTS = timeStamp - lastTS lastTS = timeStamp const deltaNow = now - lastNow lastNow = now console.log(deltaTS,deltaNow) } animate(-1)
单独运行时,deltaTS与deltaNow数值相近,timestamp精度更高。但放入大型代码库后,结果差异极大:通过requestAnimationFrame的timestamp计算的deltaTS与刷新率一致,而Date.now()计算的deltaNow波动范围从4到很大的数。我怀疑animate可能被多次触发,但仍不理解为何Date.now()的时间间隔如此异常,求问可能的原因。
动画回调被多次注册,队列堆积导致状态混乱
你怀疑animate被多次触发的方向是对的。如果代码库中重复调用animate(-1),会注册多个requestAnimationFrame回调。这些回调会在同一帧内依次执行:第一个回调把lastNow更新为当前时间,后续回调计算deltaNow时,就会得到极小值;如果主线程被阻塞导致帧推迟,下一批回调执行时deltaNow又会出现超大值。而requestAnimationFrame的timestamp是每帧统一生成的固定值,所以不管同一帧有多少个回调,deltaTS都会和刷新率匹配。主线程被长时间阻塞
大型代码库中常存在复杂DOM操作、大数据计算、同步请求等耗时任务,这些任务会阻塞主线程,导致requestAnimationFrame回调被推迟执行。Date.now()取的是当前系统时间,两次回调的实际间隔会因阻塞被拉长,deltaNow自然波动大;而requestAnimationFrame的timestamp是浏览器在帧调度节点生成的时间,即使回调被推迟,依然基于原本的帧周期计算,所以deltaTS保持稳定。全局变量被意外篡改
你代码中的lastNow、lastTS都是全局变量,大型代码库中很容易出现变量名冲突,或者其他逻辑意外修改了这些变量的值。比如某个地方重置了lastNow,下一次计算时就会得到异常的deltaNow值。系统时间波动影响Date.now()
Date.now()依赖系统时间,若系统在运行时进行NTP校时、时间调整,都会导致其返回值出现跳变;而requestAnimationFrame的timestamp基于浏览器内部的高精度单调计时器,不受系统时间调整影响,所以始终保持稳定的帧间隔。
内容的提问来源于stack exchange,提问作者pailhead

