扫码二维码的方法
- 第一微信jssdk自带的扫一扫功能,
- 优点:无兼容性问题,只要微信能扫一扫的场景,这个api都能扫。
- 缺点:只能在微信环境使用,浏览器环境中不能使用,另外一点,需要后端配合出接口,获取当前公众号的appid等信息。
- 直接用原生插件实现,发现两种插件 vue-qrcode-reader 和 @zxing/library,两个插件都可以在项目中使用,
vue-qrcode-reader在苹果手机中无法使用,第一次扫码无反应调不起摄像头,需要返回第二次进入才会正常(暂未找到解决方法),
@zxing/library安卓ios都能正常调用起摄像头,但是对于华为手机,因为华为手机有6个摄像头,正常手机就2个摄像头,所以对于华为手机要兼容判断下
如果二维码参数太多,生成的二维码密度就会越多,正常大小扫不出来,所以需要放大二维码,参数不能传太多,影响扫码准确性。- @zxing/library 和vue-qrcode-reade 库兼容问题
在装有 iOS < 14.3 相机访问仅适用于本机 Safari,而不适用于其他浏览器 (Chrome,…) 或使用 UIWebView 或 WKWebView 的应用。这不是这个库的限制,而是苹果有限的 WebRTC 支持的限制,
但是 iOS 14.3(2020 年 12 月发布)现在也支持第三方浏览器中的 WebRTC。
浏览器层使用的是 MediaDevices Web API,这是旧版浏览器不支持的。
您可以使用 WebRTC 适配器等外部 polyfill 来增加浏览器兼容性。
该库使用的是 TypedArray 等,这在较旧的浏览器(例如 Android 4 默认浏览器)中不可用。
Int32ArrayUint8ClampedArray您可以使用 core-js 添加对这些浏览器的支持
- @zxing/library 和vue-qrcode-reade 库兼容问题
qrcode-reader-vue3
- 安装指令
npm install --save qrcode-reader-vue3
- 组件中使用
<template> <div class="scan"> <qrcode-stream :camera="camera" @decode="onDecode" @init="onInit" style="height: 100vh;" > <div> <div class="qr-scanner"> <div class="box"> <div class="line"></div> <div class="angle"></div> </div> </div> </div> </qrcode-stream> </div> </template> <script lang="ts" scope> import { QrcodeStream } from "qrcode-reader-vue3"; import { defineComponent, ref, onMounted } from "vue"; export default defineComponent({ components: { QrcodeStream }, setup(props) { let camera = ref<any>(); let error = ref<string>(''); //回调扫描结果 function onDecode(result) { // 扫码结果 console.log(result) if (result !== "") { } } // 检查是否调用摄像头 async function onInit(promise) { try { const { capabilities } = await promise; console.log(capabilities); } catch (error) { if (error.name === "NotAllowedError") { error = "ERROR: 您需要授予相机访问权限"; } else if (error.name === "NotFoundError") { error = "ERROR: 这个设备上没有摄像头"; } else if (error.name === "NotSupportedError") { error = "ERROR: 所需的安全上下文(HTTPS、本地主机)"; } else if (error.name === "NotReadableError") { error = "ERROR: 相机被占用"; } else if (error.name === "OverconstrainedError") { error = "ERROR: 安装摄像头不合适"; } else if (error.name === "StreamApiNotSupportedError") { error = "ERROR: 此浏览器不支持流API"; } else if (error.name === "InsecureContextError") { error = "ERROR: 仅允许在安全上下文中访问摄像机。使用HTTPS或本地主机,而不是HTTP。"; } else { error = "ERROR:摄像机错误"; } } } return { camera, onDecode, onInit, }; }, }); </script>
注:运行在vue3.0环境,
vue-qrcode-reader再vue3.0中不生效
@zxing/library
- 安装指令
npm install @zxing/library
- 项目中使用
注:如果报错:Can’t enumerate devices, method not supported.<template> <div class="QrCode"> <video ref="video" height="100%" width="100%" id="video" autoplay></video> </div> <div class="Qr_scanner"> <div class="box"> <div class="line_row"> <div class="line"></div> </div> <div class="angle"></div> </div> </div> </template> <script lang="ts" scope> import { BrowserMultiFormatReader } from "@zxing/library"; import { defineComponent, reactive, onBeforeUnmount, onMounted, } from "vue"; export default defineComponent({ setup(props) { let codeReader = reactive(new BrowserMultiFormatReader()); onMounted(() => { openScan(); }); function openScan() { codeReader .getVideoInputDevices() .then((videoInputDevices) => { console.log("videoInputDevices", videoInputDevices, "摄像头设备"); // 默认获取第一个摄像头设备id let firstDeviceId = videoInputDevices[0].deviceId; // 根据id选择摄像头 if (videoInputDevices.length > 1) { // 华为手机有6个摄像头,前三个是前置,后三个是后置,第6个摄像头最清晰 if (videoInputDevices.length > 5) { firstDeviceId = videoInputDevices[5].deviceId; } else { // 判断是否后置摄像头 if (videoInputDeviceslablestr.indexOf("back") > -1) { firstDeviceId = videoInputDevices[0].deviceId; } else { firstDeviceId = videoInputDevices[1].deviceId; } } } decodeFromInputVideoFunc(firstDeviceId); }) .catch((err) => { alert(err) }); }; let decodeFromInputVideoFunc = (firstDeviceId: any) => { codeReader.decodeFromInputVideoDeviceContinuously( firstDeviceId, // firstDeviceId 为null 时默认选择面向环境的摄像头 "video", (result: any, err) => { if (result) { alert(result); console.log(result, "扫描结果"); } if (err && !err) { console.error(err); } } ); }; onBeforeUnmount(() => { codeReader.reset(); }); return { codeReader, }; }, }); </script> <style scoped> .QrCode { width: 100vw; height: 100vh; background: rgba(0, 0, 0, 0.2); /* background: red; */ position: relative; } #video { width: 100%; height: 100%; object-fit: cover; } /* // 二维码动画 */ .Qr_scanner { position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 9; width: 100%; height: 100%; position: relative; background-color: rgba(0, 0, 0, 0.5); } .Qr_scanner .box { width: 75vw; height: 75vw; max-height: 75vh; max-width: 75vh; position: relative; left: 50%; top: 50%; transform: translate(-50%, -50%); overflow: hidden; border: 1px solid rgb(43, 113, 254); } .line_row { width: 100%; overflow: hidden; background-image: linear-gradient( 0deg, transparent 24%, rgba(136, 176, 255, 0.1) 25%, rgba(136, 176, 255, 0.1) 26%, transparent 27%, transparent 74%, rgba(136, 176, 255, 0.1) 75%, rgba(136, 176, 255, 0.1) 76%, transparent 77%, transparent ), linear-gradient( 90deg, transparent 24%, rgba(136, 176, 255, 0.1) 25%, rgba(136, 176, 255, 0.1) 26%, transparent 27%, transparent 74%, rgba(136, 176, 255, 0.1) 75%, rgba(136, 176, 255, 0.1) 76%, transparent 77%, transparent ); background-size: 3rem 3rem; background-position: -1rem -1rem; animation: Heightchange 2s infinite; animation-timing-function: cubic-bezier(0.53, 0, 0.43, 0.99); animation-delay: 1.4s; border-bottom: 1px solid rgba(136, 176, 255, 0.1); display: flex; justify-content: center; align-items: flex-end; } .Qr_scanner .line { width: 100%; height: 3px; background: #2b71fe; filter: blur(4px); } .Qr_scanner .box:after, .Qr_scanner .box:before, .Qr_scanner .angle:after, .Qr_scanner .angle:before { content: ""; display: block; position: absolute; width: 78px; height: 78px; border: 0.3rem solid transparent; } .Qr_scanner .box:after, .Qr_scanner .box:before { top: -7px; border-top-color: #2b71fe; } .Qr_scanner .angle:after, .Qr_scanner .angle:before { bottom: -7px; border-bottom-color: #2b71fe; } .Qr_scanner .box:before, .Qr_scanner .angle:before { left: -7px; border-left-color: #2b71fe; } .Qr_scanner .box:after, .Qr_scanner .angle:after { right: -7px; border-right-color: #2b71fe; } @keyframes radar-beam { 0% { transform: translateY(-100%); } 100% { transform: translateY(0); } } @keyframes Heightchange { 0% { height: 0; } 100% { height: 100%; } } </style>
扫码能运行在https,和本地上(用http://localhost:8080/,,不要用IP:192.168.xx.xx:8080)
可以运行中vue3.0和vue2.0环境中 - 相机授权的时候可以点击拒绝,拒绝后就不能够在调取了
ZXing 库本身并不直接提供重新获取权限的专用 API。通常,JavaScript 库是无法直接在用户拒绝权限后重新触发权限请求的。
调用原生的相机监听方法:
调起相机之前先看一下用户有没有授权,如果没有授权就让他重新进入页面进行授权navigator.mediaDevices.getUserMedia({ video: true }) .then(stream => { // 用户授权了相机权限,可以进行后续操作 console.log('相机已授权') decodeFromInputVideoFunc(firstDeviceId); }) .catch(err => { // 处理错误,可能是因为权限被拒绝 console.log('获取相机权限失败:', err); });
vue-qrcode-reader
- 安装指令
npm install vue-qrcode-reader
- 组件中的应用
<template> <div class="scan"> <qrcode-stream :camera="camera" @decode="onDecode" @init="onInit" style="height: 100vh;" > <div> <div class="qr-scanner"> <div class="box"> <div class="line"></div> <div class="angle"></div> </div> </div> </div> </qrcode-stream> </div> </template> <script> // 引入 import { QrcodeStream } from "vue-qrcode-reader"; export default { // 注册 components: { QrcodeStream }, data() { return { camera: "auto", result: "", // 扫码结果信息 error: "" // 错误信息 }; }, created() {}, methods: { //回调扫描结果 onDecode(result) { if (result !== "") { this.$emit("ok", result); } }, // 检查是否调用摄像头 async onInit(promise) { try { const { capabilities } = await promise; console.log(capabilities); } catch (error) { if (error.name === "NotAllowedError") { this.error = "ERROR: 您需要授予相机访问权限"; } else if (error.name === "NotFoundError") { this.error = "ERROR: 这个设备上没有摄像头"; } else if (error.name === "NotSupportedError") { this.error = "ERROR: 所需的安全上下文(HTTPS、本地主机)"; } else if (error.name === "NotReadableError") { this.error = "ERROR: 相机被占用"; } else if (error.name === "OverconstrainedError") { this.error = "ERROR: 安装摄像头不合适"; } else if (error.name === "StreamApiNotSupportedError") { this.error = "ERROR: 此浏览器不支持流API"; } else if (error.name === "InsecureContextError") { this.error = "ERROR: 仅允许在安全上下文中访问摄像机。使用HTTPS或本地主机,而不是HTTP。"; } else { this.error = "ERROR:摄像机错误"; } this.$emit("err", this.error); } } } }; </script> <style scoped> </style>