简介:这是一份面向前端开发者的网页扫码功能插件资源,基于 HTML5 与 JavaScript 实现,可在浏览器中调用摄像头识别条形码与二维码,适用于电商商品查询、物联网设备配对、移动支付验证等需要便捷数据录入的场景。压缩包共 88 个文件,约 9.27MB,以 TypeScript 源码为主体,辅以 JavaScript 构建产物、Markdown 说明文档、PNG/GIF 演示素材及 JSON、YML 等配置文件,另含少量 HTML 示例页面,整体结构清晰,便于按模块查阅。目前已有 2127 人学习下载。资源核心为 html5-qrcode 库的完整工程,涵盖核心扫描逻辑、状态管理、原生条码检测、UI 组件与解码器等模块,并附带 Electron、Vue、HTML5 等多套示例,可帮助读者理解摄像头权限请求、实时图像识别与扫描结果回调的实现思路,快速将扫码能力集成到自己的项目中。
1. 浏览器里直接扫条码:为什么我最后选了 html5-qrcode
去年做一个仓储盘点的小工具,需求很朴素:网页上点一下按钮,调起摄像头,扫到条形码或二维码就把内容回填到输入框。听起来像是调个 API 就完事,结果我前后换了三套方案才稳定下来。第一套用原生getUserMedia加jsQR,二维码没问题,一维条形码识别率惨不忍睹;第二套换成QuaggaJS,条形码行了,二维码又拉胯,而且两个库的摄像头生命周期还得自己管,切页面就黑屏。最后落到html5-qrcode上,一个库同时吃 Code128、EAN、UPC 和 QR,摄像头启停、区域框选、错误回调都封装好了,代码量直接砍掉一半。
这篇笔记就是把这套「html+js 扫一扫条形码和二维码的插件」的落地路径讲透:它底层靠什么识别、怎么在页面里跑起来、参数怎么调才不糊、真机上那些玄学问题怎么排。适合两类人看——刚接到扫码需求、想半天出原型的前端;以及已经用了某个库但被识别率和兼容性折磨、想换方案的老手。全程不需要后端,纯浏览器侧就能跑通。
2. 选型先立住:html5-qrcode 到底替你干了什么
2.1 它不是一个识别引擎,而是三层胶水
很多人以为html5-qrcode是个识别算法库,其实它自己不做图像解码。它的定位是「摄像头 + 解码器 + UI」的编排层,拆开看是三层:
- 取流层:封装
navigator.mediaDevices.getUserMedia,处理前后置摄像头枚举、分辨率约束、facingMode切换。 - 解码层:内部默认走
ZXing-js的 wasm/纯 js 实现,二维码和一维码都覆盖;也支持你塞自定义解码器。 - 渲染层:把视频流画到 canvas 上,按你给的
qrbox裁剪出识别区域,再按fps节流抽帧送去解码。
理解这三层很关键,因为后面所有「扫不出来」的问题,你都能定位到是取流糊了、解码器不认、还是抽帧区域没框对。它对外暴露的核心就是Html5Qrcode和Html5QrcodeScanner两个类,前者给你完全控制权,后者是开箱即用的带 UI 版本。
2.2 为什么不用 jsQR 或 QuaggaJS 单干
我踩过的坑摆在这:jsQR只解二维码,一维码完全不支持,仓储场景里 Code128 是主力,直接出局。QuaggaJS一维码强,但项目维护节奏慢,二维码识别在低对比度下明显弱,而且它默认的定位框逻辑对移动端竖屏不友好。html5-qrcode的价值不是算法更强,而是把「多格式 + 摄像头管理 + 区域裁剪」打包成一个稳定接口,你不用再写胶水代码去协调两个库。
代价也要说清楚:它体积比单一解码器大,首次加载要拉 wasm 资源;识别精度受fps和qrbox影响很大,参数乱设会比裸调jsQR还慢。所以选它的前提是——你要的是「快速稳定地同时支持一维和二维」,而不是「极致轻量只扫二维码」。
2.3 环境与依赖的最小准备
它支持 script 标签直引,也支持 npm。我一般用 npm 管版本,避免 CDN 抽风。先建目录、初始化、装包:
mkdir barcode-demo && cd barcode-demo npm init -y npm install html5-qrcode # 起个静态服务,扫码必须 https 或 localhost,file:// 打不开摄像头 npx serve .装完确认node_modules/html5-qrcode里有html5-qrcode.min.js。这里有个硬约束必须提前知道:getUserMedia只在安全上下文生效,也就是https://或者http://localhost。你用file://直接双击打开 html,浏览器会静默拒绝摄像头,控制台报NotAllowedError或干脆没有mediaDevices。本地开发用npx serve或任意静态服务器都行,部署上线必须配 https。
提示:iOS Safari 对
getUserMedia的支持从 14.3 起才比较完整,且必须由用户手势(点击)触发,不能在DOMContentLoaded里自动调,否则直接失败。
3. 最小可跑:20 行代码把摄像头和识别框拉起来
3.1 页面骨架与容器约定
html5-qrcode需要一个容器 div 来挂视频和 canvas,id 你自己定,但要保证初始化时这个 div 已经存在于 DOM。下面是最小页面:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <!-- 移动端必须加 viewport,否则视频会被缩放糊掉 --> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>扫码 Demo</title> </head> <body> <div id="reader" style="width: 100%; max-width: 480px;"></div> <p>结果:<span id="result">等待扫描…</span></p> <script src="./node_modules/html5-qrcode/html5-qrcode.min.js"></script> <script src="./app.js"></script> </body> </html>容器宽度建议给个上限,比如 480px。原因在 3.3 会讲——视频渲染尺寸和识别区域是联动的,容器太宽在桌面端反而让qrbox比例失真。
3.2 初始化扫描器与回调
app.js里用Html5Qrcode类手动控制,比Html5QrcodeScanner更可控:
// 拿到容器元素,后续所有渲染都挂在这 const readerEl = document.getElementById('reader'); const resultEl = document.getElementById('result'); // 第二个参数 true 表示开启详细日志,调试期打开,上线可关 const html5Qrcode = new Html5Qrcode('reader', /* verbose= */ true); const config = { fps: 10, // 每秒抽帧解码次数,10 是移动端流畅与识别的平衡点 qrbox: { width: 250, height: 250 }, // 识别区域,见 3.3 aspectRatio: 1.0, // 视频宽高比,方形区域配 1.0 最稳 disableFlip: false, // 允许镜像翻转,前置摄像头扫二维码时需要 }; function onScanSuccess(decodedText, decodedResult) { resultEl.textContent = decodedText; // 扫到就停,避免重复回调刷屏;需要连续扫再手动 start html5Qrcode.stop().then(() => console.log('已停止扫描')); } function onScanFailure(error) { // 这个回调每帧没识别到都会触发,不要在这里弹窗或打大量日志 } html5Qrcode.start( { facingMode: 'environment' }, // 优先后置摄像头 config, onScanSuccess, onScanFailure ).catch((err) => { console.error('启动失败:', err); resultEl.textContent = '摄像头启动失败,请检查权限或 https'; });逻辑说明:start第一个参数是摄像头约束,facingMode: 'environment'表示后置;第二个是识别配置;后两个是成功/失败回调。onScanFailure触发极其频繁,每帧没扫到都会调,所以里面千万别写console.log或 DOM 操作,否则主线程会被拖垮,表现为「越扫越卡」。
参数说明:fps从 10 往上调会增加 CPU 占用,低端安卓机调到 15 以上就明显发热掉帧;disableFlip设false是为了兼容前置摄像头的镜像画面,如果你只用后置可以设true省一点计算。
3.3 qrbox 怎么设才不糊
qrbox是识别区域,不是显示区域。它决定从视频帧里裁哪一块送去解码。设太小,条码没完整进框就永远扫不到;设太大,解码器要在更多像素里找码,速度下降。
我的经验值:手机竖屏下width取容器宽度的 60%~70%,height取同值保持方形。如果你要扫一维条形码,条形码是横向长条,方形框反而容易切掉两端,这时候用函数式动态计算:
// 根据容器实际宽度动态算识别框,避免硬编码在小屏上溢出 const boxSize = Math.min(readerEl.clientWidth, 480) * 0.7; const config = { fps: 10, qrbox: (viewfinderWidth, viewfinderHeight) => { const size = Math.floor(Math.min(viewfinderWidth, viewfinderHeight) * 0.7); return { width: size, height: size }; }, };qrbox支持传函数,入参是取景器的宽高,返回{width, height}。这样在不同屏幕上自适应,不用为每个机型写死。注意一维码场景可以把返回的height压到width的 0.4 倍左右,让框更扁,贴合条码形状。
4. 参数调优与多格式识别:让一维码和二维码都稳
4.1 一维码和二维码的识别差异
二维码有定位图案,任意角度、部分遮挡都能解;一维码靠黑白条宽度编码,对角度和清晰度敏感得多。这决定了参数策略不同:
| 维度 | 二维码 | 一维条形码 |
|---|---|---|
| 最佳角度 | 任意 | 尽量水平,倾斜超 30° 识别率骤降 |
| 对焦要求 | 中 | 高,糊一点就废 |
| qrbox 形状 | 方形 | 扁矩形更合适 |
| fps 敏感度 | 低 | 高,需要更多帧碰运气 |
所以同一个页面要同时支持两者时,我一般把fps提到 12~15,qrbox用扁矩形,牺牲一点二维码速度换一维码成功率。
4.2 用 supportedFormats 收窄解码范围
默认它什么格式都试,格式越多单帧解码越慢。如果你明确只扫 Code128 和 QR,就显式声明,能提速:
import { Html5Qrcode, Html5QrcodeSupportedFormats } from 'html5-qrcode'; const html5Qrcode = new Html5Qrcode('reader', { formatsToSupport: [ Html5QrcodeSupportedFormats.QR_CODE, Html5QrcodeSupportedFormats.CODE_128, Html5QrcodeSupportedFormats.EAN_13, ], verbose: false, });formatsToSupport是构造时传的,不是start时传,这点容易搞错。收窄后单帧解码耗时能降 20%~40%,低端机上体感明显。代价是没声明的格式扫不出来,所以上线前确认业务覆盖的码制。
4.3 摄像头切换与 torch 补光
仓储或暗光环境,补光能救命。html5-qrcode支持申请 torch(手电筒)能力:
// 启动后拿到当前摄像头的 capabilities,尝试开补光 async function toggleTorch(on) { try { await html5Qrcode.applyVideoConstraints({ advanced: [{ torch: on }], }); } catch (e) { console.warn('该设备不支持 torch:', e); } }applyVideoConstraints是运行时改视频约束的入口,torch 属于 advanced 约束,不是所有设备都支持,安卓 Chrome 支持较好,iOS 基本没有。切换前后置用html5Qrcode.getRunningTrackCameraCapabilities()配合switchCamera,但更稳的做法是stop之后用新的facingMode重新start,避免部分机型切换后画面卡死。
5. 避坑与排查:真机上那些让人抓狂的问题
5.1 现象:本地能扫,部署后摄像头打不开
原因:非安全上下文。getUserMedia在 http 域名下被浏览器直接禁用,只有 localhost 例外。解决:全站上 https,证书用正规 CA 的,自签证书在移动端会被拦。排查时先看navigator.mediaDevices是否为undefined,是就基本确定是上下文问题。
5.2 现象:iOS 上视频黑屏但没报错
原因:iOS Safari 要求getUserMedia必须在用户手势的调用栈里触发,页面加载自动调会被静默拦截。解决:把start包在按钮的 click 回调里,别放window.onload。另外 iOS 对playsinline有要求,html5-qrcode内部已处理,但如果你自己叠了 video 标签要手动加playsinline属性。
5.3 现象:扫着扫着越来越卡,手机发烫
原因:onScanFailure里写了日志或 DOM 操作,或者fps设太高。每帧失败回调都执行重操作,主线程直接堵死。解决:清空onScanFailure内容,fps压到 10~12,扫到结果立刻stop。我一般还会在页面隐藏时监听visibilitychange主动停流,避免后台空转。
5.4 现象:一维码怎么都对不上,二维码秒出
原因:qrbox是方形,把横向条码两端裁掉了;或者条码倾斜。解决:换扁矩形识别框,提示用户水平对准;同时确认formatsToSupport里包含对应码制(比如 CODE_128、EAN_13),没声明的话解码器根本不会尝试。
5.5 现象:前置摄像头扫出来是镜像,内容对但方向反
原因:前置摄像头默认镜像预览,二维码本身能解,但如果你把画面截图存档就会反。解决:disableFlip保持false让解码器处理翻转;如果只是显示问题,用 CSStransform: scaleX(-1)反转预览层即可,不影响解码。
6. 进阶:把扫码做成可复用组件与连续扫描
走到这一步,基本功能已经稳了。但真实项目里往往还要连续扫多个码、要暂停恢复、要在扫到后做防抖。我最后把它封成了一个类,核心是控制start/stop的生命周期,避免重复初始化导致的内存泄漏。
class BarcodeScanner { constructor(elementId, onDecoded) { this.elementId = elementId; this.onDecoded = onDecoded; this.instance = null; this.lastText = ''; this.lastTime = 0; } async start() { // 每次启动都新建实例,避免复用旧实例的残留状态 this.instance = new Html5Qrcode(this.elementId, { verbose: false }); await this.instance.start( { facingMode: 'environment' }, { fps: 12, qrbox: (w, h) => ({ width: Math.floor(w * 0.7), height: Math.floor(h * 0.5) }) }, (text) => { const now = Date.now(); // 防抖:同一内容 1.5 秒内不重复回调,连续扫场景必备 if (text === this.lastText && now - this.lastTime < 1500) return; this.lastText = text; this.lastTime = now; this.onDecoded(text); }, () => {} // 失败回调留空,别写逻辑 ); } async stop() { if (this.instance) { await this.instance.stop(); this.instance.clear(); // 清掉 canvas 和 video,释放资源 this.instance = null; } } }这个封装里有两个关键点值得单独说。第一是防抖:连续扫描模式下,同一个码会被连续识别几十次,不防抖的话业务层会收到大量重复回调,库存数量直接翻倍。1.5 秒是我试出来的经验值,太短挡不住,太长影响扫下一个码的节奏。第二是clear():stop()只是停流,DOM 里的 canvas 和 video 还在,反复 start/stop 会累积节点,clear()负责清干净,这一步不做,长时间运行必然内存泄漏。
验证方法上,我一般用三组码做回归:一张标准 QR、一张 Code128 标签、一张低对比度的 EAN-13 商品码。分别在桌面 Chrome、安卓 Chrome、iOS Safari 上各扫 20 次,记录成功率和平均耗时。成功率低于 90% 就回去调qrbox和fps,别急着换库——多数时候是参数问题不是库的问题。
最后说个我自己的习惯:任何扫码功能上线前,我一定会在真实暗光环境(比如关灯的仓库角落)测一遍,因为实验室灯光下所有方案都好看,暗光才是照妖镜。补光能力能开就开,开不了就把fps降下来让每帧曝光更充分。这套东西我前后调了两周,血泪经验就是——别迷信库,参数和场景匹配才是识别率的大头。希望帮到你。
本文还有配套的精品资源,点击获取