audio标签需显式添加controls属性才显示原生进度条,但iOS Safari等浏览器限制拖动反馈;如需稳定可定制进度条,应移除controls,用配合timeupdate和input事件手动同步currentTime与value。

audio 元素自带进度条,但默认不显示
HTML 的 <audio></audio> 标签本身支持播放、暂停、音量和进度控制,但浏览器只在用户交互(如点击播放)后才可能显示原生控件,且是否显示进度条取决于浏览器实现和 controls 属性是否开启。不加 controls 就完全没进度条;加了也不保证所有环境都渲染一致(比如 iOS Safari 原生控件无拖动反馈,Android Chrome 可能隐藏时间轴)。
实际开发中,如果需要稳定、可样式化、可编程控制的进度条,必须手动实现——用 <input type="range"> + JavaScript 监听 timeupdate 和 seeking 事件。
用 input range 搭配 timeupdate 实现可拖拽进度条
核心思路是:把音频当前播放时间映射到 range 的 value,把 range 的滑动映射回音频的 currentTime。关键点不是“怎么画”,而是“怎么同步”。
-
<input type="range">的min设为0,max动态设为audio.duration(需等loadedmetadata后读取) - 监听
audio.timeupdate事件更新 range 的value,避免跳帧导致进度条卡顿 - 监听 range 的
input事件(非change)实时拖动,同时设置audio.currentTime = range.value - 拖动过程中建议临时禁用
timeupdate更新,否则会出现“拉一下跳两下”的抖动
示例片段:
立即学习“前端免费学习笔记(深入)”;
<audio id="player" src="song.mp3"></audio>
<input type="range" id="progress" min="0" value="0">
<script>
const audio = document.getElementById('player');
const progress = document.getElementById('progress');
audio.addEventListener('loadedmetadata', () => {
progress.max = audio.duration || 0;
});
audio.addEventListener('timeupdate', () => {
progress.value = audio.currentTime;
});
progress.addEventListener('input', () => {
audio.currentTime = progress.value;
});
</script>
iOS Safari 和部分 Android 浏览器的兼容性坑
iOS Safari 对 <audio></audio> 的控制权限极严:无法通过 JS 主动触发播放(必须用户手势),且 currentTime 设置后不一定立刻生效,duration 在未加载元数据前为 NaN,甚至拖动后 timeupdate 可能延迟触发或丢失。
- 务必监听
loadedmetadata再初始化max,否则 range 会卡死在 0–0 区间 - 拖动后建议监听
seeked事件确认定位完成,再恢复timeupdate同步 - 某些安卓 WebView 中
input事件不触发,需 fallback 到change+ 点击模拟 - 不要依赖
audio.readyState >= 2来判断能否拖动,iOS 上它常卡在 1(HAVE_METADATA)
为什么不用自定义 canvas 进度条?
Canvas 虽然灵活,但对音频进度条属于过度设计:它无法原生响应触摸/鼠标拖动事件,需要自己实现 hit-test、缩放适配、无障碍支持(ARIA)、键盘导航(左右键微调、Home/End 跳首尾),且在高 DPI 屏幕上容易模糊。而 <input type="range"> 天然支持 touch/mouse/keyboard,语义正确,屏幕阅读器可识别,样式也能用 ::-webkit-slider-thumb 等伪类定制。
真正难的不是“画出来”,是让拖动响应及时、定位准确、跨设备行为一致——这些都藏在事件时机和浏览器策略里,而不是 DOM 结构里。



















