video和audio元素是媒体宿主而非容器,原生controls不可定制,需移除controls属性并用外部DOM结构包裹媒体元素,通过监听timeupdate、loadedmetadata、ended、volumechange等事件同步UI,确保video为顶层子节点,注意audio无默认尺寸及iOS播放限制。

video 和 audio 元素本身不是“容器”,而是媒体宿主(media provider)——它们不接受子元素包裹控制 UI,所有原生控件(播放按钮、进度条等)由浏览器私有渲染,无法用 CSS 选择器直接穿透修改。
要实现可维护的自定义播放器,必须放弃依赖 controls 属性,转而用 DOM 结构包裹媒体元素,并手动绑定状态与行为。
移除 controls 后如何保持功能完整性
原生
controls是个黑盒:样式不可控、事件不可拦截、移动端手势逻辑不透明(比如 iOS 上双指缩放会触发全屏)-
关键动作必须通过 JS 显式调用:
play()、pause()、load()、canplay等事件监听不可省略立即学习“前端免费学习笔记(深入)”;
必须监听以下基础事件才能同步 UI:
timeupdate(更新进度条宽度)loadedmetadata(获取duration,否则currentTime / duration会 NaN)ended(重置播放按钮状态或自动循环)volumechange(同步音量滑块位置和静音图标)不要只靠
setInterval更新进度条——它不准、耗性能、且在页面后台时仍运行;timeupdate是浏览器内部驱动的唯一可靠来源
自定义结构中 video 元素的位置必须是顶层子节点
-
错误写法:
<div class="player"> <div class="ui-layer">...</div> <video><source src="a.mp4"></video> </div>
这样没问题;但若写成:<div class="player"> <video> <div class="ui-layer">...</div> <source src="a.mp4"> </video> </div>
——所有自定义 DOM 将被浏览器忽略,video只认source、track等合法子元素 video内部不能插任何非媒体语义标签;所有控制 UI 必须放在其外部,用绝对定位或 flex 布局覆盖
音频/视频共用结构时需注意的兼容性断点
-
audio默认无尺寸,width/height属性无效;需用 CSS 设定宽高或留白容器 -
video在 iOS Safari 中,play()必须由用户手势触发(click/touchend),且首次调用后才能设currentTime;否则会静默失败 -
muted+autoplay是目前唯一稳定触发自动播放的组合,但仅限video;audio即使静音,在多数移动端仍被禁止 autoplay -
preload="metadata"应始终设置——它让loadedmetadata尽快触发,避免 UI 卡在“00:00 / --:--”状态
真正难的不是画一个播放按钮,而是让 video.currentTime 的赋值在所有设备上都生效、让拖动进度条的坐标换算不因缩放或 DPR 偏移、让键盘用户能用空格键切播放/暂停、让屏幕阅读器知道当前是“已暂停”而非“按钮未响应”。这些细节不写进结构里,后期维护成本会指数级上升。



















