开发背景
最开始公司说到这个需求的时候第一个想法是使用微信的 js SDK 接入微信的扫一扫。但是得知所做的这个功能模块是需要嵌入到之前开发的app中,所以就只有使用 js 原生的 API 来开发了。最后选用了 navigator.mediaDevices.getUserMedia
用来获取摄像头的视频流,通过 canvas
展示到页面上,同时获取到 canvas
生成的图片对象数据,传递个 jsQR
这个开源库进行二维码的解析。
功能开发完成之后我就在 uniapp 插件社区中,把我写的组件给发布了。发布之后大受欢迎,同时也收到了各种反馈,如:识别二维码速度过慢、识别范围小、能否有一直识别、可调用闪关灯吗等问题。后面一直在持续更新,完善了很多的功能。
之后换了一家公司也接到了类型的需求,但是没有选用 uniapp
这个技术栈,而是选用了 vue3+TS
的开发规范。没有办法只有把之前写好的组件进行了重写。
最近闲下来后,发现在网络上真正分享这个功能的文章比较少,要么说的不清不楚的,要么就还是在使用 navigator.getUserMedia
这个已经废除的 API 规范,要么就是不兼容苹果系统,还有就是大量复制的水文章。
所以决定写一篇文章来说说怎么实现这个功能,并且使用原生 js
再次重写了这个功能。一下代码都使用原生 js
进行讲解。同时提供 uniapp
、vue3+TS
、原生js
的 demo 下载。
demo 演示 与 下载
当前只开发手机页面版,请使用手机直接扫码查看 demo 演示
地址:h5plugin.mumudev.top/#/pages/get…
使用到的相关技术
-
navigator.mediaDevices.getUserMedia
用于唤起摄像头,并且获取到摄像头的视频流数据文档地址:MediaDevices.getUserMedia()
此 API 必须要 HTTPS 环境才能调用,所以就必须要我们的测试环境与生成环境都配置 HTTPS,测试环境如果是 VUE 或者 UNIAPP 就直接在 DEV SERVER 中进行配置就可以了,原生的话推荐使用 VS CODE 中的 Live Server 插件生成假的证书进行配置与启动服务
"liveServer.settings.https": { "enable": true, "cert": "D:\\vsCode\\https\\example.com+5.pem", //本地证书地址 "key": "D:\\vsCode\\https\\example.com+5-key.pem", //本地密钥地址 "passphrase": "" },
-
jsQR.js
用于对图片中的二维码进行解析GitHub地址:cozmo/jsQR
一款大佬写的开源库。
-
canvas 标签
这个算是 HTML 基础吧 -
viod 标签
基础中的基础
开始写代码
这里就使用原生HTML写法给大家讲解当中的核心代码,完整的mode与其他框架的代码可以在上面小程序中进行下载
完整的mode中都是封装好的代码,可直接开箱即用。
// index.html
<div id="mumuQrcode">
<!-- canvas 用于展示视频流与识别到的二维码位置 -->
<canvas id='canvas'></canvas>
<!-- 中间的扫描框 -->
<div class="box">
<div class="line"></div>
<div class="angle"></div>
</div>
</div>
<!-- 先引入 jsQR 库,后引入自己写的代码 -->
<script src="./jsQR.js"></script>
<script src="./index.js"></script>
// index.js
// 判断当前是否是 https 环境
if (origin.indexOf('https') === -1) throw '请在 https 环境中调用摄像头。'
// 获取当前屏幕可用的宽高,用于设置全屏扫描
const windowWidth = document.documentElement.clientWidth
const windowHeight = document.documentElement.clientHeight
// 获取页面上的 canvas 组件,用上面获取的宽高来设置 canvas 为全屏,并且创建出 画布
const canvas = document.getElementById('canvas')
canvas.width = windowWidth
canvas.height = windowHeight
// 创建画布
const canvas2d = canvas.getContext('2d')
// 创建一个 video 标签,并且设置 video 的尺寸为全屏,用于接收摄像头的数据
const video = document.createElement('video')
video.width = windowWidth
video.height = windowHeight
// 设置 video 标签不显示进度条
video.setAttribute('playsinline', 'true')
video.setAttribute('webkit-playsinline', 'true')
// 设置调用摄像头的参数
// 调用摄像头时的分辨率,摄像头的分辨率是以横屏计算的,所以要把屏幕的高度当作视频宽度,屏幕的宽度当作视频高度度,这样就可以获取到一个和手机物理分辨率一样的视频尺寸。这里是可以传递比分辨率高的数字,只要尺寸的比例是一样即可
const width = windowHeight
const height = windowWidth
const videoParam = {
audio: false,
video: {
facingMode: {
/** 使用后置还是前置摄像头 后:environment 前:user*/
exact: 'environment'
},
width, // 刚刚设置的宽度
height // 刚刚设置的高度
}
}
// 调用摄像头。此 api 是一个 promise
navigator.mediaDevices.getUserMedia(videoParam).then(stream => {
// 回调中回返回 stream 获取到的视频流数据
// 把视频流数据给到 video 中
video.srcObject = stream
// 播放 video
video.play()
// 调用下面写的解析函数
tick()
})
// 解析函数,用于截取视频每一帧来解析二维码
function tick() {
// 判断video中是否加载好视频流数据,只有加载好后才解析
if (video.readyState === video.HAVE_ENOUGH_DATA) {
// 把视频的每一帧截取画到刚刚创建的画布上
canvas2d.drawImage(video, 0, 0, windowWidth, windowHeight)
// 通过 canvas画布 获取到当前每一帧的 图片对象 数据
const imageData = canvas2d.getImageData(0, 0, windowWidth, windowHeight)
// 把 图片对象 数据交给 jsQR 这个库进行解析
const codeRes = jsQR(imageData.data, imageData.width, imageData.height, {
inversionAttempts: 'dontInvert'
})
// 判断是否获取到二维码数据
if (codeRes && codeRes.data) {
// codeRes.data 中就是二维码中的数据了
console.log(codeRes.data)
alert(codeRes.data)
}
}
// 每当这个函数执行完后,马上就执行这个函数
// requestAnimationFrame 这个方法可以百度一下
requestAnimationFrame(tick)
}
大功告成
进测试。发现现代中的浏览器都支持,微信内部与跨平台app中也完美调用。
不支持的浏览器有:
-
苹果系统下的 阿里系浏览器(UC、夸克等)
使用的 Webkit 内核较低,而且还会抓取视频流数据,导致无法正常预先
-
open 浏览器
不知道具体原因,听说国内的open没有采用他自己的内核
-
IE
IE就不多说了
附带一张兼容图