要修正 setinterval 漂移,需放弃固定间隔逻辑,改用基于真实时间戳的 settimeout 递归校准机制:记录起始时间,每次计算 drift 并动态调整下一次延迟;结合 visibilitychange 处理页面不可见;锚定服务器时间避免本地时钟偏差。

setInterval 本身不校准,只机械重复;累计误差来自每次回调执行耗时、主线程阻塞或页面不可见导致的延迟叠加。要修正漂移,核心不是“修 setInterval”,而是放弃它固有的时间推演逻辑,改用基于真实时间戳的动态校准机制。
记录起始时间并每次比对偏差
在定时器启动时记录一个基准时间(如 Date.now()),后续每次执行都计算“本该触发的时间点”与“实际触发时间”的差值(即 drift),再用这个差值动态调整下一次触发间隔。
- 例如:设定每 1000ms 执行一次,第 3 次本该在
startTime + 3000触发,但实际在startTime + 3042才执行 → drift = +42ms - 下一次目标时间为
startTime + 4000,当前已到startTime + 3042,剩余理论等待时间为4000 − 42 = 3958ms,但需立即安排 setTimeout 延迟958ms(因为已过 3042ms) - 关键:不依赖固定间隔,而用
targetTime − Date.now()计算真实延迟
用 setTimeout 递归替代 setInterval
setInterval 的“固定间隔”是误差根源;换成手动控制的 setTimeout 递归链,才能插入校准逻辑。
- 每次回调结束时,计算下一次目标时间(如
expected += interval) - 再用
Math.max(0, expected − Date.now())算出本次应延迟毫秒数 - 这样即使某次回调慢了 80ms,下一次就会自动少等 80ms,抵消漂移
处理页面不可见场景(息屏/切后台)
浏览器会暂停或降频定时器,导致大量 tick 丢失。仅靠 drift 校准不够,必须结合 visibilitychange 事件感知状态变化。
- 监听
document.hidden变化,在页面隐藏时记录hiddenAt = Date.now() - 恢复可见时,读取当前时间
now,计算休眠时长now − hiddenAt - 将休眠时间直接加到计时逻辑中(如倒计时直接减去休眠秒数,而非靠 setTimeout 补 tick)
- 这对电商秒杀、直播倒计时等强时效场景必不可少
锚定服务器时间避免本地时钟偏差
用户可随意修改系统时间,导致所有基于 Date.now() 的计算失准。真正可靠的基准只能来自服务端。
- 初始化时请求一次
/api/time获取服务器时间戳,并记录客户端与服务端的时间差 - 所有“应该发生的时间点”都基于服务器时间推算,而非本地时间
- 为防长期运行后网络延迟或服务端时钟漂移,可每隔 30–60 秒轻量同步一次时间差











