90%摄像头黑屏或报错源于权限拒绝、非https协议、设备占用;须确保https/localhost、用户手势触发、video加载完成后再drawimage,并优先选用jsqr库。

用 MediaDevices.getUserMedia 获取摄像头流但黑屏或报错
浏览器不给权限、HTTPS 缺失、设备被占用,这三类原因占了 90% 的「打不开摄像头」问题。非 HTTPS 环境下 getUserMedia 直接拒绝调用,本地开发用 localhost 可以绕过,但 file:// 协议一定失败。
- 检查控制台是否报
NotAllowedError或SecurityError—— 前者是用户拒权,后者基本就是协议问题 - 确保页面运行在
https://或http://localhost下,否则直接放弃调试 - 用
navigator.mediaDevices.enumerateDevices()看设备列表里是否有videoinput,避免误用音频设备 - 别在
DOMContentLoaded里立刻调getUserMedia,等用户手势(如按钮点击)触发,否则 Chrome 会静默拒绝
用 CanvasRenderingContext2D.drawImage 抓帧但解码失败
扫码本质是「持续从视频流抽帧 → 转成图像数据 → 交给解码库识别」。很多人卡在 drawImage 后得到的 canvas 是空的,或者 getImageData 返回全黑 —— 这通常是因为视频还没开始播放就画了。
- 必须监听
video元素的loadeddata事件后再首次 draw,否则 canvas 内容未初始化 - canvas 尺寸要和 video 实际渲染尺寸一致(不是
video.width属性值),建议用video.videoWidth/video.videoHeight - 频繁调用
drawImage+getImageData会卡顿,建议用requestAnimationFrame控制频率(比如 15fps 足够),别用setInterval - 部分安卓 WebView 对
getImageData返回的Uint8ClampedArray处理异常,可先用canvas.toDataURL('image/png')回退验证是否真有图像
选 jsQR 还是 quaggaJS?
quaggaJS 已停止维护,最后发布是 2019 年,对现代浏览器兼容性差,尤其在 iOS Safari 上常因 WebGL 权限或 OffscreenCanvas 缺失崩溃;jsQR 是当前事实标准,纯 JS 实现、无依赖、支持 Data URL / ImageData / Uint8Array 输入,体积小(gzip 后约 25KB)。
本程序源码为asp与acc编写,并没有花哨的界面与繁琐的功能,维护简单方便,只要你有一些点点asp的基础,二次开发易如反掌。 1.功能包括产品,新闻,留言簿,招聘,下载,...是大部分中小型的企业建站的首选。本程序是免费开源,只为大家学习之用。如果用于商业,版权问题概不负责。1.采用asp+access更加适合中小企业的网站模式。 2.网站页面div+css兼容目前所有主流浏览器,ie6+,Ch
- 别碰
quaggaJS,哪怕文档看着“功能多”,实际跑不起来的概率远高于jsQR -
jsQR解码前需把 canvas 数据转成灰度Uint8Array,它不接受 RGB 四通道数组,漏掉这步会返回null - 传入的图像宽高最好在 300–600px 区间:太小特征不足,太大计算慢且手机发热明显
- 解码失败时别立即重试,加个简单防抖(如 500ms 内忽略重复结果),避免同一帧反复识别出错
iOS Safari 扫码失败的硬限制
iOS 14.5+ 强制要求视频流必须启用 mediaStreamTrack.applyConstraints({ advanced: [{ focusMode: 'manual' }] }) 才能获得清晰帧,否则默认自动对焦延迟高、边缘模糊,jsQR 直接无法识别。这不是 bug,是 Apple 的隐私策略:模糊帧更难用于人脸识别。
立即学习“前端免费学习笔记(深入)”;
- 拿到
MediaStreamTrack后,立刻调用applyConstraints设置{ focusMode: 'manual' },即使你不需要手动调焦 - 某些旧版 iOS 不支持该约束,需包裹
try/catch,失败则降级但保留基础流程 - 别依赖
video.play()的Promise结果来判断是否就绪 —— iOS 上它可能提前 resolve,但画面仍是黑的,仍得靠loadeddata - 全屏模式(
video.webkitEnterFullscreen())在部分 iOS 版本会中断媒体流,扫码界面尽量用 inline 播放









