必须用 Web Audio API 实现淡入,因 audio.volume 直接修改受浏览器限制:iOS 静音拦截、Safari 无反应、volume 离散无插值、时间精度差;需用户手势创建 AudioContext、用 createMediaElementSource 接入、基于 ctx.currentTime 调节 gain。

audio 标签本身不支持淡入,直接改 volume 会卡顿、被 iOS 静音、且在 Safari 里根本没反应——这不是你代码写错了,是浏览器限制死的。
为什么 audio.volume += 0.01 不行
常见错误现象:audio.volume = 0 后用 setTimeout 每 50ms 加一次音量,结果在 Safari 上无声,在 Chrome 里有明显跳变声。
- 浏览器对非用户手势触发的音量变更会静音拦截(尤其 iOS)
-
volume是离散属性,没有时间轴插值,无法保证每帧平滑过渡 - 音频播放状态(
canplay、playing)未同步时,刚设完volume就被重置 - 移动端后台标签页中,
setTimeout和requestAnimationFrame会被节流,时间严重偏移
必须用 Web Audio API 的三个硬性前提
真正能做淡入的只有 Web Audio API,但它不是“拿来即用”,得满足三件事:
-
AudioContext必须在用户手势(如click、touchend)回调中首次创建,否则处于suspended状态,后续所有gain调用都无效 - 要用
ctx.createMediaElementSource(audio)接入 HTML 音频,不能自己解码 Buffer(除非你处理的是预加载好的AudioBuffer) - 所有时间点必须基于
ctx.currentTime计算,不能用Date.now()或performance.now(),否则在低性能设备或后台页误差可达 200ms+
淡入 2 秒的最小可行代码(含防错)
以下代码已绕过 iOS 静音、时间精度、资源泄漏三类高频问题:
将 Markdown 文章转换为微信公众号兼容的纯 HTML 格式。当用户要求"转成微信格式"、"生成公众号文章 HTML"、"排版到微信草稿箱"、"微信粘贴格式"时触发。输出带内联样式的纯 HTML,微信编辑器可直接渲染,无需额外适配。支持标题、段落、列表、粗体、行内代码、表格、引用块、代码块、配图的完整转换。
const audio = document.querySelector('audio');
let ctx, source, gainNode;
function initAudio() {
if (ctx) return;
ctx = new (window.AudioContext || window.webkitAudioContext)();
source = ctx.createMediaElementSource(audio);
gainNode = ctx.createGain();
source.connect(gainNode);
gainNode.connect(ctx.destination);
}
document.querySelector('#play-btn').addEventListener('click', () => {
initAudio(); // 用户点击才初始化
audio.play().catch(e => console.warn('play failed:', e));
});
function fadeIn(duration = 2000) {
if (!ctx || ctx.state === 'suspended') return;
const now = ctx.currentTime;
gainNode.gain.setValueAtTime(0, now);
gainNode.gain.setTargetAtTime(1, now, duration / 3000); // timeConstant ≈ 0.67
}
注意:setTargetAtTime 的第三个参数是 timeConstant(单位秒),不是持续时间;常用值在 0.1~0.5 之间。传 0.67 是为让增益在约 2 秒内自然趋近 1,听感更稳。
立即学习“前端免费学习笔记(深入)”;
容易被忽略的关键点
很多人写了 setTargetAtTime 却没效果,往往卡在这几个地方:
- 没在用户点击后调用
ctx.resume()(iOS Safari 必须显式恢复) - 音频还没触发
canplay就调了fadeIn(),导致ctx.currentTime偏小甚至为 0 - 多次点击播放按钮重复执行
initAudio(),但没断开旧连接,造成 GainNode 泄漏和音量叠加 - 忘记监听
ended事件,在淡出后立即play()新音频,iOS 下大概率失败


















