|
网页调用手机摄像头扫二维码,调用最直接的手机摄像方案是 navigator.mediaDevices.getUserMedia 拿到视频流,再用 BarcodeDetector 或者 jsQR 这类库去解析。头扫听起来简单,描维码实际踩坑的调用地方不少。 先看核心代码骨架。手机摄像HTML 部分只需要一个 <video> 元素和一个 <canvas>,头扫canvas 不一定展示出来,描维码但解析帧的调用时候要用: <video id="video" autoplay playsinline></video> <canvas id="canvas" hidden></canvas> 注意 playsinline 这个属性。iOS Safari 里不加它,手机摄像视频会强制全屏播放,头扫整个页面布局直接崩掉。描维码我第一次在 iPhone 上测试时就卡在这儿,调用查了半天以为是手机摄像 getUserMedia 的问题,其实是头扫视频元素的行为差异。 JS 这边,获取摄像头大致长这样: const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } }); video.srcObject = stream; facingMode: 'environment' 告诉浏览器优先用后置摄像头。桌面端无所谓,但手机上不指定的话,默认打开前置,扫二维码得把手机翻过来对着自己,体验很怪。 拿到视频流之后,解析二维码有两条路。 路线一:BarcodeDetector API。 这是浏览器原生的条形码/二维码识别接口,Chrome 在 Android 上支持得不错,用法极简: const detector = new BarcodeDetector({ formats: ['qr_code'] }); const codes = await detector.detect(video); 但问题在于兼容性。截至我写这篇东西的时候,iOS Safari 还不支持 BarcodeDetector,Firefox 也不支持。所以如果你的用户里有 iPhone,这条路走不通。 路线二:jsQR。 这是一个纯 JS 的二维码解析库,把视频帧画到 canvas 上,取出 ImageData 传给它: const ctx = canvas.getContext('2d'); ctx.drawImage(video, 0, 0, canvas.width, canvas.height); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const code = jsQR(imageData.data, imageData.width, imageData.height); if (code) { console.log(code.data); } jsQR 的好处是兼容性广,iOS 上也能跑。代价是性能——每帧都做一次完整解析,中低端手机上帧率掉得厉害。实际项目里我会加个 requestAnimationFrame 节流,比如隔两帧解析一次,肉眼几乎感觉不到延迟,CPU 占用降不少。 还有一个容易忽略的点:HTTPS。 getUserMedia 在非安全上下文下直接报错,本地开发用 localhost 没问题,一旦部署到线上,没有 HTTPS 连摄像头都打不开。这不是建议,是硬性要求。 另外,用户授权被拒绝之后,getUserMedia 抛出的错误类型是 NotAllowedError。你得捕获它,给用户一个提示,而不是让页面静默失败。我见过一些页面在这块完全没处理,用户点了“拒绝”之后页面一片空白,根本不知道发生了什么。 实际用的时候,我会把 canvas 尺寸设成 640×480 左右,不用跟视频原始分辨率一致。二维码解析不需要那么高的像素,缩小之后反而更快,识别率也没明显下降。这个尺寸是我试过几轮之后觉得比较平衡的值。 整套方案不依赖任何原生 App,纯 Web 就能跑。缺点是首次加载需要用户授权,而且光线暗、二维码歪斜的时候识别率会下降。真要做得稳,还得加个手电筒开关——track.applyConstraints({ advanced: [{ torch: true }] }),不过这个只在部分 Android 机型上有效。 |