video.poster 是首选方案,因浏览器原生支持、无需JS时机控制、避免跨域和白屏问题,且MP4/H.264视频首帧即关键帧;若必须截帧,须确保crossOrigin、正确canvas尺寸、toBlob导出及blob URL有效。

直接用 video.poster 就行,别写 JS 截帧——除非你明确需要第 1 秒、第 3 秒等非首帧,或必须导出 PNG/JPEG 文件。
为什么 video.poster 是首选
浏览器原生支持,不依赖 JS 执行时机,不触发跨域报错,不卡在白屏里等加载。只要视频是 MP4/H.264 封装(绝大多数上传场景),首帧就是关键帧(IDR),poster 能直接显示它。
使用方式极其简单:
<video controls poster="<url-to-first-frame>"> <source src="your-video.mp4" type="video/mp4"> </video>
上传后生成封面 URL 的逻辑也轻量:拿到 File 对象 → 创建 URL.createObjectURL(file) → 把这个 URL 直接赋给 poster 属性即可。不需要 canvas、不监听事件、不设 currentTime。
立即学习“前端免费学习笔记(深入)”;
currentTime = 0 为什么截出来是黑的
H.264/H.265 视频的关键帧(IDR)极少落在时间轴 0ms,通常在 0.03–0.1s 区间。设 currentTime = 0 后立刻 drawImage,canvas 拿到的是未解码的空帧。
- 必须监听
seeked事件,不是loadeddata或canplay—— 只有seeked表示 seek 已完成、画面数据真正就绪 - 设
currentTime = 0.1比0更稳妥,尤其对 MP4 封装视频 - iOS Safari 需先调
video.play().then(() => video.pause())触发解码,否则video.videoWidth可能为 0
真要用 canvas 截帧,这几个配置缺一不可
一旦涉及 drawImage 和 toBlob(),以下四点必须同时满足,否则大概率报错或返回黑图/空字符串:
-
<video crossOrigin="anonymous">属性必须加,且服务端响应头必须含Access-Control-Allow-Origin: *(或具体域名)。本地file://协议无法绕过此限制 - canvas 宽高必须设为
video.videoWidth/video.videoHeight,不能用clientWidth或 CSS 尺寸,否则拉伸失真 - 导出优先用
canvas.toBlob(callback, 'image/jpeg', 0.9),不用toDataURL()—— iOS 上后者易内存溢出,且体积大、无法直接赋给poster - 用户上传的 blob URL 必须确保没被提前释放(比如
URL.revokeObjectURL()调太早)
移动端(尤其微信 iOS)的典型坑
微信内置浏览器在 iOS 上常出现两个连锁问题:一是 loadedmetadata 不触发;二是触发后 video.videoWidth 为 0。
绕过方式是:先 play() 再 pause(),强制触发解码流程:
video.play().then(() => {
video.pause();
// 此时 video.videoWidth / video.videoHeight 多数已就绪
// 再设 currentTime = 0.1,监听 seeked,drawImage
});
但注意:这个 play() 必须由用户手势(如点击上传按钮)触发,否则会被静音策略拦截。
真正难的不是“怎么截”,而是“什么时候算准备好”——loadedmetadata 不代表能画,canplay 不代表已解码,seeked 才是那个临界点。很多 bug 都卡在这一步的时机判断上。



















