setinterval 不适合做精确节拍器,因其受事件循环阻塞、页面后台暂停和cpu负载影响,误差常超±20ms;真实节拍器需用 web audio api 基于 audiocontext.currenttime 的绝对时间调度,配合 visibilitychange 检测与bpm对齐策略实现亚毫秒级精度。

为什么 setInterval 不适合做精确节拍器
浏览器的 setInterval 实际执行间隔受事件循环阻塞、页面后台暂停、CPU 负载影响,误差常达 ±20ms 以上,4/4 拍下 120BPM(500ms 一拍)时,几秒后就会明显偏移。真实节拍器必须绕过 JS 定时器抖动,依赖音频硬件时序。
用 Web Audio API 实现亚毫秒级节拍触发
核心是用 AudioContext 的 currentTime 做绝对时间锚点,而非相对延迟。每次播放前计算下一拍的绝对时间戳,再用 start(absoluteTime) 精确调度。
- 初始化时调用
audioContext.resume()解除静音策略(尤其 Chrome 中首次交互前禁止音频) - 节拍音推荐用短脉冲波(
oscillator.type = 'square'),持续时间控制在0.02秒内,避免拖尾干扰节奏感 - 不要在循环里反复创建
OscillatorNode和GainNode,复用节点并只调用start()/stop() - 示例关键逻辑:
const nextTick = audioContext.currentTime + intervalSec;<br>oscillator.start(nextTick);<br>oscillator.stop(nextTick + 0.02);
处理页面失焦或系统休眠导致的节拍丢失
用户切到其他标签页时,requestAnimationFrame 和 setTimeout 会被限频甚至暂停,但 AudioContext.currentTime 仍持续推进。因此需主动检测跳拍:
- 每拍检查
audioContext.currentTime是否已超过预期时间,若偏差 >intervalSec * 0.5,说明漏播,立即补一拍并重置下次时间戳 - 监听
visibilitychange事件:页面隐藏时暂停逻辑,显示时用当前currentTime推算最近该响的拍点,避免“突然后补一堆滴” - 不依赖
pagehide——Safari 对该事件支持不稳定,优先用document.hidden轮询
UI 响应与 BPM 实时调节的陷阱
滑块(<input type="range">)拖动时高频触发事件,若每次变动都重算节拍周期,会导致音频调度混乱。正确做法是只在松手(input 事件结束)后更新,并重新对齐到下一个整拍:
- 用
clearTimeout(pendingReset)防止连续拖拽堆积未执行的重置逻辑 - 新 BPM 生效时机必须落在「当前拍之后的第一个整数倍拍点」,例如当前在第 3.7 拍,新间隔为 0.4s,则从第 4.0 拍开始应用,而非立刻切
- DOM 更新(如指针旋转、LED 闪烁)和音频调度必须分离:UI 可用
requestAnimationFrame平滑渲染,但绝不参与节拍计时
实际最难的部分不是播放声音,而是让视觉反馈、用户交互、音频时序三者在不同浏览器生命周期下始终对齐。尤其是 Safari 的音频上下文自动挂起策略、移动端后台标签页的定时器冻结,稍不注意就变成“看着准、听着歪”。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











