1. 为什么H5扫码功能这么难搞?
最近在做一个uniapp的H5项目,产品经理突然说要加个扫码功能。我当时第一反应就是直接调用uni.scanCode()不就完事了?结果一查文档傻眼了——这个API在H5环境下根本不支持!后来才知道,H5环境下调用摄像头涉及到浏览器安全策略,各家浏览器实现还不一样,坑特别多。
我试过好几个方案,最开始用qrcode-decoder这个npm包,识别率低得感人,稍微模糊点的二维码就歇菜。后来换成qrcode.js,效果好了不少,但还是要处理一堆兼容性问题。实测下来,这套方案在微信内置浏览器、Chrome、Safari上都能跑,算是比较稳的选择。
2. 手把手教你引入qrcode.js
2.1 获取qrcode.js文件
首先得搞到qrcode.js这个库。推荐直接从GitHub或者Gitee下载,我用的这个版本:
// 新建一个空js文件,把下面代码复制进去 (function(r){r.qrcode=function(...){...}})(window);这个库体积小(才几十KB),不依赖其他库,特别适合H5项目。下载后放到项目的static/js目录下,或者任何你觉得合适的位置。
2.2 在uniapp中引入
在vue页面的script部分这样引入:
// #ifdef H5 import '@/static/js/qrcode.js' // #endif注意要用条件编译,因为只有H5环境需要这个库。小程序和App端可以直接用uni.scanCode()。
3. 实现相册扫码功能
3.1 选择图片的实现
uniapp的uni.chooseImage()方法用起来特别方便:
uni.chooseImage({ count: 1, // 只选一张 sourceType: ['album'], // 只从相册选 success: (res) => { this.scanQRCode(res.tempFilePaths[0]) } })这里有个坑要注意:res.tempFiles和res.tempFilePaths的区别。在H5环境下,tempFiles是File对象,tempFilePaths是blob URL,我们解码需要的是后者。
3.2 图片转URL的兼容写法
不同浏览器创建对象URL的方式不一样,得写个兼容方法:
getObjectURL(file) { if (window.URL && window.URL.createObjectURL) { return window.URL.createObjectURL(file) } else if (window.webkitURL) { return window.webkitURL.createObjectURL(file) } return null }这个方法会把File对象转成blob://开头的临时URL,qrcode.js需要这个URL来解码。
3.3 调用解码功能
拿到图片URL后就可以解码了:
scanQRCode(imageUrl) { const qr = new Qrcode() qr.decode(imageUrl) qr.callback = (result) => { if (result.includes('error')) { uni.showToast({ title: '识别失败', icon: 'none' }) } else { this.handleScanResult(result) } } }实测发现,如果图片不是二维码,callback会返回"error decoding QR Code"这样的字符串,所以用includes判断比较稳妥。
4. 实现拍照扫码功能
4.1 调用相机拍照
和相册选择类似,改个参数就行:
uni.chooseImage({ count: 1, sourceType: ['camera'], // 调起相机 success: (res) => { this.scanQRCode(res.tempFilePaths[0]) } })4.2 解决H5调用摄像头的限制
这里有个大坑:H5调用摄像头必须满足以下条件之一:
- 网站是https协议
- 本地开发用localhost或127.0.0.1
- iOS 15.4+支持http调用摄像头
如果是在微信内置浏览器,还需要配置JS-SDK的白名单。建议开发阶段用localhost测试,上线一定要用https。
4.3 提升拍照识别率的技巧
- 拍照时让用户保持手机稳定
- 建议用户让二维码占满取景框的60%以上
- 光线要充足,避免反光
- 可以加个"重拍"按钮,识别失败时方便重试
5. 常见问题解决方案
5.1 识别率低的优化方案
如果发现识别率不理想,可以试试这些方法:
- 预处理图片:
// 先创建Image对象压缩图片 const img = new Image() img.onload = () => { const canvas = document.createElement('canvas') // 控制图片大小在1000px以内 const scale = Math.min(1, 1000/Math.max(img.width, img.height)) canvas.width = img.width * scale canvas.height = img.height * scale const ctx = canvas.getContext('2d') ctx.drawImage(img, 0, 0, canvas.width, canvas.height) this.scanQRCode(canvas.toDataURL('image/jpeg')) } img.src = imageUrl- 尝试不同的解码参数:
// qrcode.js支持配置解码参数 const qr = new Qrcode({ inversionAttempts: 'attemptBoth', // 尝试识别反色二维码 canOverwriteImage: true // 允许覆盖图片 })5.2 浏览器兼容性问题
- iOS Safari的问题:
- 需要用户主动触发事件(比如点击按钮)才能调起相机
- 第一次使用会弹出权限询问,要做好引导
- 安卓微信浏览器:
- 可能需要配置JS-SDK
- 部分机型有兼容性问题,建议真机测试
- PC端浏览器:
- Chrome和Edge支持最好
- Firefox需要额外配置
- IE就别想了,直接提示用户换浏览器
5.3 性能优化建议
- 大图片处理:
// 限制图片大小 if (file.size > 2 * 1024 * 1024) { uni.showToast({ title: '图片太大,请选择2M以内的图片', icon: 'none' }) return }- 内存释放:
// 用完后释放URL对象 const url = this.getObjectURL(file) this.scanQRCode(url) // 解码完成后 window.URL.revokeObjectURL(url)- 避免重复实例化:
// 可以在created生命周期创建实例 created() { this.qrDecoder = new Qrcode() }6. 完整代码示例
6.1 页面模板部分
<template> <view class="container"> <button @click="chooseImage('album')">从相册选择二维码</button> <button @click="chooseImage('camera')">拍照识别二维码</button> <view v-if="result" class="result">识别结果:{{result}}</view> </view> </template>6.2 脚本部分
<script> // #ifdef H5 import '@/static/js/qrcode.js' // #endif export default { data() { return { result: '' } }, methods: { chooseImage(sourceType) { uni.chooseImage({ count: 1, sourceType: [sourceType], success: res => { const url = this.getObjectURL(res.tempFiles[0]) this.scanQRCode(url) } }) }, getObjectURL(file) { if (window.URL && window.URL.createObjectURL) { return window.URL.createObjectURL(file) } else if (window.webkitURL) { return window.webkitURL.createObjectURL(file) } return null }, scanQRCode(imageUrl) { uni.showLoading({ title: '识别中...', mask: true }) // #ifdef H5 const qr = new Qrcode() qr.decode(imageUrl) qr.callback = (result) => { uni.hideLoading() if (result.includes('error')) { uni.showToast({ title: '识别失败,请重试', icon: 'none' }) } else { this.result = result // 释放URL对象 window.URL.revokeObjectURL(imageUrl) } } // #endif // #ifndef H5 uni.scanCode({ success: res => this.result = res.result }) // #endif } } } </script>6.3 样式部分
<style> .container { padding: 20px; } button { margin-bottom: 15px; } .result { margin-top: 20px; word-break: break-all; } </style>7. 进阶方案:使用html5-qrcode
如果项目对扫码体验要求更高,可以试试html5-qrcode这个库。它支持实时摄像头扫码,体验接近原生。
安装:
npm install html5-qrcode使用示例:
import { Html5Qrcode } from 'html5-qrcode' // 创建实例 const html5QrCode = new Html5Qrcode("reader") // 开始扫码 html5QrCode.start( { facingMode: "environment" }, { fps: 10, qrbox: 250 }, (decodedText) => { console.log(decodedText) }, (error) => { console.warn(error) } ) // 停止扫码 html5QrCode.stop()这个方案的优点是体验好,缺点是包体积大(约200KB),而且必须https环境。适合对扫码体验要求高的项目。
8. 项目实战经验分享
在最近的一个电商项目中,我们遇到了这样的需求:H5页面需要支持扫描商品条形码。经过多次尝试,最终方案是:
- 优先使用html5-qrcode实现实时扫码
- 在不支持的环境下,降级到qrcode.js+相册选择
- 针对条形码做了特别优化:
// 配置识别条形码 const qr = new Qrcode({ readers: ['qrcode', 'ean_reader'] // 增加条形码识别器 })遇到的坑:
- 安卓微信浏览器下,第一次调用相机可能会卡顿
- iOS 15+需要处理新的权限弹窗
- 低端手机上大图片解码会卡死
优化后的效果:
- 主流机型识别率提升到90%+
- 平均识别时间控制在1秒内
- 内存占用减少30%