video 元素必须添加 controls 属性才能渲染原生控件;播放按钮须用 type="button" 的 button 元素;进度条需结合 timeupdate 事件与 input[type="range"] 实现平滑拖动;iOS Safari 要求 preload="metadata" 且首次 play() 必须由用户手势触发。

video 元素必须带 controls 属性才能触发原生控件渲染
不加 controls,浏览器不会显示任何按钮或进度条——哪怕你写了 JS 逻辑,用户也看不到交互入口。这不是 bug,是规范行为:HTML5 规定 <video> 默认无 UI,controls 是唯一启用原生控件的开关。
常见错误是以为“自己写按钮就够了”,结果在移动端测试时发现点击没反馈,其实是视频容器本身没加载控件层,导致 touch 事件穿透或焦点丢失。
-
controls是布尔属性,写成controls=""或controls效果一样 - 开发阶段建议先保留
controls,验证视频能播、能拖、能静音,再逐步用 JS 替换对应功能 - 若要完全自定义 UI,必须显式移除
controls,否则原生控件和你的按钮会重叠或冲突
播放/暂停按钮必须用
用 <div onclick="..."> 写播放按钮,在键盘导航、屏幕阅读器、空格键触发等场景下直接失效。这不是兼容性“优化项”,而是基础可访问性门槛。
真实报错往往不是控制台里冒红字,而是用户反馈“按空格没反应”“Tab 键跳过按钮”——这些无法靠 console.log 捕获。
立即学习“前端免费学习笔记(深入)”;
文章转信息图。将文章/笔记转化为手机可读的 HTML 信息图,自动匹配视觉风格。触发场景:文章转图、笔记转图、信息图、转小红书图、做张图、可视化这篇文章、文生图。
- 写法必须是
<button type="button" id="playBtn">▶</button>,type="button"防止意外提交表单 - 监听逻辑里别只判断
video.paused,要配合video.readyState >= 4,否则视频元数据没加载完时调用play()会静默失败 - 移动端 Safari 要求首次
play()必须由用户手势触发,所以按钮 click 事件里调用video.play()是安全的,但window.onload里调就必然失败
进度条要用 input[type="range"] + timeupdate 事件联动
仅靠 input 的 change 或 input 事件更新进度,会导致拖动卡顿、跳帧、松手后回弹——因为这些事件触发频率太低,且不反映实时播放位置。
真正平滑同步靠的是 timeupdate:它每 200–250ms 触发一次,足够人眼感知连续变化。
- 设置
max值为 100,用(video.currentTime / video.duration) * 100计算百分比并赋给progressBar.value - 拖动时监听
input事件(非change),立刻计算跳转时间:video.currentTime = (e.target.value / 100) * video.duration - 注意
video.duration初始为NaN,需等loadedmetadata事件后才可用,否则进度条会卡在 0
移动端 iOS Safari 必须配 preload="metadata"
桌面端正常、手机点不动,大概率是 preload 没设对。iOS Safari 对 preload="none" 或未声明 preload 的视频,会延迟加载元数据,导致 video.duration 长期为 NaN,paused 始终返回 true,所有状态判断失灵。
这不是 JS 写错了,是浏览器策略差异——安卓 Chrome 和桌面 Safari 相对宽松,iOS 最严。
- 写死
preload="metadata",确保至少加载时长、宽高、编码信息 - 避免同时开启原生
controls和 JS 控制,某些 iOS 版本会接管播放逻辑,你的按钮 click 会被忽略 - 如果用了
autoplay,务必搭配muted,否则 iOS 直接拦截,连第一次播放都触发不了
实际构建时,最易被忽略的是 loadedmetadata 事件的等待时机——所有依赖 duration、videoHeight 的逻辑(比如缩放适配、预加载判断)都得挂在这个事件之后,而不是 DOM 加载完就执行。


















