必须通过 HTTPS 或 localhost 调用 navigator.mediaDevices.getUserMedia({video: true}) 获取媒体流,绑定到 video.srcObject 并调用 play();拍照需监听 canplay 事件后 drawImage,注意镜像、横竖屏及权限回收。

用 navigator.mediaDevices.getUserMedia() 获取摄像头流
直接调用系统摄像头,核心是获取媒体流,不是“打开设备”这种抽象操作。必须走 getUserMedia(),且需 HTTPS 或 localhost 环境,否则浏览器会静默拒绝——本地双击打开 index.html(即 file:// 协议)必然失败,这是最常卡住的地方。
常见错误现象:NotAllowedError: Permission denied 或控制台完全没提示、video 元素黑屏。原因八成是协议不合规或用户点了“禁止”。
实操建议:
- 用
python3 -m http.server 8000或 VS Code Live Server 启动本地服务,确保地址是http://localhost:8000起头 - 请求时明确指定
{ video: true },不要只写true;若还需音频,才加audio: false显式关闭(避免部分安卓设备默认要麦克风权限) - 务必用
try/catch包裹,getUserMedia()是 Promise,拒绝时会抛错,不捕获就静默失败
把视频流绑定到 <video> 标签并触发播放
拿到 MediaStream 后不能直接“显示”,得赋给 video 元素的 srcObject 属性,再手动调用 play()。旧写法 src = URL.createObjectURL(stream) 已废弃,现代浏览器会警告甚至报错。
立即学习“前端免费学习笔记(深入)”;
容易踩的坑:
-
video元素没设autoplay属性也不影响,但必须主动调play(),否则在 Chrome/Firefox 移动端大概率被拦截 - 如果
play()报NotAllowedError,大概率是用户交互未触发(比如在页面加载完成回调里直接播),得绑定到按钮点击等用户动作内 - 别忘了加
playsinline属性,iOS Safari 不加这个,视频会在全屏播放器里打开,无法和 canvas 同屏操作
用 <canvas> + drawImage() 拍照并导出图片
拍照本质是把当前视频帧画到 canvas 上,再转成图片数据。关键点不在“拍”,而在“取哪一帧”——必须等 video 元素自然尺寸稳定后(即 loadeddata 或 canplay 事件后)再调用 drawImage(),否则 canvas 可能为空白或拉伸变形。
实操建议:
- 监听
video的canplay事件,确认视频元数据已加载,此时video.videoWidth和video.videoHeight才可靠 -
drawImage()第一参数是video元素,后四个参数控制裁剪区域,建议用0, 0, video.videoWidth, video.videoHeight原始尺寸抓取,避免因 CSS 缩放导致模糊 - 导出用
canvas.toDataURL('image/jpeg', 0.9),JPEG 比 PNG 小很多,0.9 是画质与体积的合理平衡点;注意 iOS Safari 对toBlob()支持较晚,如需兼容老版本,优先用toDataURL
处理移动端横竖屏、镜像与权限回收
手机拍照时,video 默认是镜像(自拍模式),但 canvas 抓帧不会自动翻转,导致照片左右颠倒;另外,用户旋转手机后,videoWidth/Height 可能互换,但 CSS 渲染角度不变,造成构图错乱。
关键细节:
- 用 CSS
transform: scaleX(-1)对video做水平翻转,让预览看起来正常;但 canvas 绘制时仍用原始流,这样拍出来的图才是用户“看到的”方向 - 不要依赖
window.orientation,改用screen.orientation.angle或监听resize+ 检查video.videoWidth > video.videoHeight动态调整 canvas 尺寸 - 拍照后建议调用
stream.getTracks().forEach(t => t.stop())主动释放摄像头,尤其在单页应用中切路由时,否则 Android 设备可能锁死摄像头,下次进页面打不开
真正麻烦的是权限模型:Chrome 94+ 对重复调用 getUserMedia() 会跳过弹窗直接复用上次选择,但用户手动撤销权限后,再调用会静默失败——得检查 MediaDevices.enumerateDevices() 里是否有可用视频设备,再决定是否引导用户去设置页手动开启。


















