必须用 Web Audio API 实现淡入淡出:AudioContext 需用户手势创建,用 createMediaElementSource 接入 audio,所有时间点基于 context.currentTime 计算,通过 GainNode.gain.setTargetAtTime 实现平滑过渡。

HTML <audio></audio> 元素自身不支持淡入淡出,直接改 audio.volume 会卡顿、被 iOS 静音、且必然出现爆音或跳变——这不是你代码写错了,是浏览器限制死的。
为什么 audio.volume += 0.01 会失败
常见错误现象:audio.volume = 0 后用 setTimeout 每 50ms 加一次音量,在 Safari 上完全没声音,Chrome 里有明显“咔哒”声。
- iOS 和部分安卓浏览器会拦截非用户手势触发的音量变更,直接静音
-
volume是离散属性,没有时间轴插值能力,浏览器不保证每帧平滑过渡 -
audio的播放状态(canplay、playing)未同步时,刚设完volume就被重置 - 后台标签页中
setTimeout和requestAnimationFrame被节流,时间误差可达 200ms+
必须用 Web Audio API 的三个硬性前提
真正能做淡入淡出的只有 Web Audio API,但它不是“拿来即用”,得满足三件事:
-
AudioContext必须在用户手势(如click、touchend)回调中首次创建,否则处于suspended状态,后续所有gain调用都无效 - 要用
context.createMediaElementSource(audio)接入 HTML 音频,不能绕过元素自己解码(除非你处理的是预加载好的AudioBuffer) - 所有时间点必须基于
context.currentTime计算,不能用Date.now()或performance.now(),否则低性能设备或后台页误差极大
用 GainNode.gain.setTargetAtTime 实现自然淡入
这是最常用、听感最自然的方式,按指数曲线逼近目标值,无需手动管理定时器:
立即学习“前端免费学习笔记(深入)”;
- 淡入:先调
gainNode.gain.setValueAtTime(0, context.currentTime),再调gainNode.gain.setTargetAtTime(1, context.currentTime, 0.2) -
timeConstant = 0.2表示约 600ms 内从 0 增至 0.99,值越小响应越快,但低于 0.05 容易生硬 - 淡出同理:调
gainNode.gain.setTargetAtTime(0, context.currentTime, 0.2),之后用setTimeout在0.2 * 3 ≈ 600ms后调audio.pause()并断开连接 - 每个音频源必须配独立
GainNode,共用一个会导致音量叠加或干扰
容易被忽略的关键细节
很多人卡在最后一步:淡出结束时只调 audio.pause(),但 GainNode 仍连着,音频图持续运行,资源不释放;也有人在 setTargetAtTime(0) 后立刻 disconnect(),导致最后一段衰减被截断。
正确做法是:淡出终点先 gainNode.gain.setValueAtTime(0.001, context.currentTime) 彻底静音,再 audio.pause(),最后在 setTimeout 回调里执行 gainNode.disconnect() 和 source.disconnect()。iOS Safari 下,新音频想立刻 play(),必须等上一个 ended 事件或显式调 audio.load()。



















