应优先使用 video.poster 属性截首帧,若需 canvas 截图则必须监听 seeked 事件、设 currentTime ≥ 0.1、确保 CORS 配置正确、canvas 尺寸用 video.videoWidth/Height、导出用 toBlob()。

直接用 poster 属性就行,别一上来就写 JS 截帧。绝大多数场景下它更轻量、更可靠,不依赖加载时机、不触发跨域报错、不卡在白屏里等 JS 执行。
为什么 currentTime = 0 截出来的 canvas 是黑的
因为 H.264/H.265 视频的 IDR 帧(可独立解码的关键帧)通常不在时间轴 0ms,而是在 0.03–0.1s 区间。设 currentTime = 0 后立刻 drawImage,实际拿到的是未解码的空帧或黑场。
- 必须监听
seeked事件,不是loadeddata或canplay—— 只有seeked表示 seek 操作真正完成,画面数据已就绪 - 设
video.currentTime = 0.1比0更稳妥,尤其对 MP4 封装的视频 - 若视频是用户上传的 blob URL,要确保
URL.createObjectURL(file)已执行,且 blob 未被提前释放
crossOrigin="anonymous" 必须配服务端 CORS 头
跨域视频(比如 CDN 上的 MP4)不加配置,drawImage 会污染 canvas,导致 toDataURL() 报错或返回空字符串,错误信息是:SecurityError: Failed to execute 'toDataURL' on 'HTMLCanvasElement'。
- 前端
<video></video>标签必须带crossorigin="anonymous"属性 - 服务端响应头必须包含
Access-Control-Allow-Origin: *(或具体域名),否则即使写了crossorigin也无效 - 本地文件(
file://协议)无法绕过此限制,开发时建议起本地服务(如http-server)
canvas 尺寸和导出格式怎么选才不糊、不崩
用 video.clientWidth 或 CSS 宽高来设 canvas 尺寸,会导致截图拉伸、裁剪失真;导出用 toDataURL() 在 iOS 上容易内存溢出或截断。
立即学习“前端免费学习笔记(深入)”;
- 画布宽高应设为
video.videoWidth/video.videoHeight,这是原始分辨率,最准 - 导出优先用
canvas.toBlob()而非toDataURL():体积小、不占内存、可直接赋给video.poster - 首帧无透明需求时,选
'image/jpeg'+ 质量参数0.9;要透明通道才用'image/png' - iOS Safari 需要先调
video.play().then(() => video.pause())触发解码,否则videoWidth可能为 0
真正难的不是“怎么截”,而是“什么时候算准备好”——loadedmetadata 不代表能画,canplay 不代表已解码,seeked 才是那个临界点。很多人卡在黑帧上,不是代码写错了,是没等到真正的就绪信号。



















