浏览器后台会节流定时器,web worker 配合 page visibility api 和时间戳轮询可实现高精度定时:主线程监听 visibilitychange 并通知 worker 暂停/恢复,worker 用 basetime + pausedduration 动态计算触发时刻,定期同步校准,推荐使用 worker-timers 库。
标签页切到后台时,浏览器会强制降低定时器频率,主线程的 setinterval 可能被压到 1 秒甚至更慢——这不是 bug,是设计。web worker 本身也不免疫节流,但它是唯一能绕过前台依赖、构建可控时间逻辑的载体。关键不是“让定时器不停”,而是“知道它停了多久,并在恢复时补上节奏”。
用 Page Visibility API 主动感知挂起状态
Worker 无法读取 document.hidden,必须由主线程承担监听职责:
- 主线程监听
visibilitychange事件,一检测到document.hidden === true,立刻postMessage({ type: 'pause', timestamp: Date.now() }) - 切回前台时,发送
{ type: 'resume', timestamp: Date.now() } - Worker 内维护
pausedDuration = 0,每次 resume 都累加currentTimestamp - lastPauseTime
改用时间戳轮询替代固定间隔定时器
别再写 setInterval(fn, 1000)。Worker 中应建立逻辑时钟模型:
- 初始化时记录
baseTime = Date.now(),作为所有任务的参考起点 - 用短周期
setTimeout(..., 50)或requestIdleCallback轮询当前真实时间 - 每次轮询计算“该触发时刻”:
baseTime + interval * count + pausedDuration - 仅当
Date.now() >= 应触发时刻时执行任务,并递增count
定期同步校准防止长期漂移
单靠本地累计会有误差累积,尤其长时间后台后:
- 主线程每 30 秒向 Worker 发送一次
{ type: 'sync', serverTime: xxx, localTime: Date.now() } - Worker 根据消息往返估算延迟,动态调整
baseTime偏移量(例如:取serverTime - (localTime + rtt/2)作为修正值) - 对倒计时类场景,还可将剩余时间直接同步,避免纯推算偏差
优先选用 worker-timers 这类成熟方案
重复造轮子易出错。worker-timers 已封装上述全部逻辑:
- 安装:
npm install worker-timers - 导入后可直接替换原生 API:
import { setInterval } from 'worker-timers' - 它内部自动处理 visibility 感知、暂停累计、时间同步和 ID 管理,调用方式与原生一致
- 实测表明:后台标签页中仍可稳定维持 100ms 级精度,无跳变、无堆积










