getUserMedia.访问摄像头(扫描以及上传图片)解析二维码
话不多说先上效果图:
1.准备工作
引用解析二维码的js插件:qrcode.js。
核心:qrcode.js中的qrcode.decode方法和qrcode.callback方法
2.通过getUserMedia方法调用摄像头进行扫描解析
首先你要了解getUserMedia的使用方法,这是MDN上的解释:getUserMedia语法。
注意:getUserMedia必须在HTTPS协议下才能访问摄像头,并且有的浏览器不支持getUserMedia方法,在这里推荐大家使用Chrome浏览器。
- html代码:
<button type="button" class="btn btn-primary" onclick="showPrintModal()">扫描</button>
<div class="modal fade" id="_printModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title"></h4>
</div>
<div class="modal-body" style="position:relative">
<div class="qrcode-box" style="position:absolute;left:50%;transform:translateX(-50%);width:300px;height:300px">
<span></span>
<span></span>
<span></span>
<span></span>
</div>
<video style="width: 100%;height:300px"></video>
<canvas class="hidden" width="300" height="300"></canvas>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
- css代码:
.qrcode-box:after {
-webkit-animation: rotateAnim linear 3.68s infinite;
animation: rotateAnim linear 3.68s infinite;
content: '';
position: absolute;
display: block;
width: 100%;
height: 30px;
background-image: linear-gradient(rgba(0,0,0,0),rgba(31,162,255,1) 90%);
}
/*二维码上下移动的扫描横线*/
@keyframes rotateAnim {
from {
top: 0;
}
to {
top: calc(100% - 30px);
}
}
@-webkit-keyframes rotateAnim {
from {
top: 0;
}
to {
top: calc(100% - 30px);
}
}
/*二维码扫描框的四角*/
.qrcode-box span {
width: 24px;
height: 24px;
position: absolute;
border: 4px solid #1fa2ff;
}
.qrcode-box span:nth-child( 1) {
left: -4px;
top: -4px;
border-width: 4px 0 0 4px;
}
.qrcode-box span:nth-child( 2) {
right: -4px;
top: -4px;
border-width: 4px 4px 0 0;
}
.qrcode-box span:nth-child( 3) {
right: -4px;
bottom: -4px;
border-width: 0 4px 4px 0;
}
.qrcode-box span:nth-child( 4) {
left: -4px;
bottom: -4px;
border-width: 0 0 4px 4px;
}
- js代码:
//二维码扫描模态框
function showPrintModal() {
//closeVideo:接收视频流对象序列;canvasImg:接收定时器
var closeVideo, canvasImg;
var video = $('#_printModal video')[0];
var canvas = $('#_printModal canvas')[0];
var context = canvas.getContext('2d');
//页面切换关闭模态框
$(document).on("visibilitychange", function () {
var page = this.visibilityState;
if (page == "hidden") {
$('#_printModal').modal('hide');
}
});
//getUserMedia兼容性处理
var getUserMedia = (constraints, success, error) => {
if (navigator.mediaDevices.getUserMedia) {
//最新的标准API
navigator.mediaDevices.getUserMedia(constraints).then(success).catch(error);
} else if (navigator.webkitGetUserMedia) {
//webkit核心浏览器
navigator.webkitGetUserMedia(constraints).then(success).catch(error);
} else if (navigator.mozGetUserMedia) {
//firfox浏览器
navigator.mozGetUserMedia(constraints).then(success).catch(error);
} else if (navigator.getUserMedia) {
//旧版API
navigator.getUserMedia(constraints).then(success).catch(error);
}
}
var capture = () => {
//canvas上生成video里的图像
context.drawImage(video, 0, 0, 300, 300);
//toDataURL会返回一个data-URI(base64)然后对此进行解析
qrcode.decode(canvas.toDataURL('image/png'));
//解析图片结果回调
qrcode.callback = (e) => {
//解析失败都会返回error decoding QR Code"
if (e != "error decoding QR Code") {//二维码解析成功
//停止访问摄像头
closeVideo.stop();
//停止定时器
clearInterval(canvasImg);
//清空canvas上的图片
context.clearRect(0, 0, canvas.width, canvas.height);
//显示二维码内容
alert(e);
}
}
}
//调用用户媒体设备, 访问摄像头
if (navigator.mediaDevices.getUserMedia || navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia) {
//所在浏览器支持getUserMedia就调用
getUserMedia({
video: {
width: 500,
height: 500,
//user前置摄像头
facingMode: { exact: "environment" }
}//基础配置,MDN上还有其他的介绍
}, (stream) => {//getUserMedia访问摄像头成功的回调
//显示模态框
$('#_printModal').modal("show");
//video接收摄像头捕获的视频流
video.srcObject = stream;
//视频流在video标签中播放
video.play();
//使用closeVideo变量接收视频流对象序列,用来之后的关闭操作
closeVideo = stream.getTracks()[0];
//定时器:每隔0.5s执行capture方法,在canvas上生成图片进行解析
canvasImg = setInterval(capture, 500);
}, () => {//getUserMedia访问摄像头失败的回调
alert("访问用户媒体设备失败");
});
} else {//浏览器不存在getUserMedia方法
alert('不支持访问用户媒体');
}
}
核心的思路是:在html界面创建video标签和canvas标签,使用getUserMedia方法调用摄像头在video标签中播放,然后每个0.5s获取video标签上的图像,在canvas标签上生成当时获取到的图像图片,通过toDataURL得到图片的data-URI(base64)。最后使用解析二维码插件qrcode.js中的qrcode.decode方法进行解析,解析之后qrcode.callback会返回解析的结果。
我们可以封装成一个函数,在实际应用中直接使用new Promise方法获取解析的结果,其他地方要使用扫描二维码的时候,直接通过new Promise调用此方法即可,方便快捷:
//二维码扫描模态框
function showPrintModal(resolve) {
var modalId = '_printModal';
var html =
`<div class="modal fade" id="${modalId}" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title"></h4>
</div>
<div class="modal-body" style="position:relative">
<div class="qrcode-box" style="position:absolute;left:50%;transform:translateX(-50%);width:300px;height:300px">
<span></span>
<span></span>
<span></span>
<span></span>
</div>
<video style="width: 100%;height:300px"></video>
<canvas class="hidden" width="300" height="300"></canvas>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>`;
var modal = $(html);
$("body").append(modal);
var closeVideo, canvasImg, qrCode = null;
var video = $('#_printModal video')[0];
var canvas = $('#_printModal canvas')[0];
var context = canvas.getContext('2d');
modal.on('hidden.bs.modal',
function () {
if (closeVideo) {
closeVideo.stop();
}
if (canvasImg) {
clearInterval(canvasImg);
}
resolve(qrCode);
modal.remove();
});
$(document).on("visibilitychange", function () {
var page = this.visibilityState;
if (page == "hidden") {
modal.modal('hide');
}
});
var getUserMedia = (constraints, success, error) => {
if (navigator.mediaDevices.getUserMedia) {
//最新的标准API
navigator.mediaDevices.getUserMedia(constraints).then(success).catch(error);
} else if (navigator.webkitGetUserMedia) {
//webkit核心浏览器
navigator.webkitGetUserMedia(constraints).then(success).catch(error);
} else if (navigator.mozGetUserMedia) {
//firfox浏览器
navigator.mozGetUserMedia(constraints).then(success).catch(error);
} else if (navigator.getUserMedia) {
//旧版API
navigator.getUserMedia(constraints).then(success).catch(error);
}
}
var capture = () => {
context.drawImage(video, 0, 0, 300, 300);
qrcode.decode(canvas.toDataURL('image/png'));
qrcode.callback = (e) => {
//结果回调
if (e != "error decoding QR Code") {
qrCode = e;
clearInterval(canvasImg);
context.clearRect(0, 0, canvas.width, canvas.height);
modal.modal('hide');
}
}
}
if (navigator.mediaDevices.getUserMedia || navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia) {
//调用用户媒体设备, 访问摄像头
getUserMedia({
video: {
width: 500,
height: 500,
//user前置摄像头
facingMode: { exact: "environment" }
}
}, (stream) => {
modal.modal("show");
video.srcObject = stream;
video.play();
closeVideo = stream.getTracks()[0];
canvasImg = setInterval(capture, 500);
}, () => {
modal.modal('hide');
layer.msg("访问用户媒体设备失败");
});
} else {
modal.modal('hide');
layer.msg('不支持访问用户媒体');
}
}
new Promise调用:
new Promise(function (resolve) {
showPrintModal(resolve);
}).then((result) => {
alert(result);//扫描解析成功返回的数据
});
3.上传图片解析二维码
html代码:
<input type="file" id="file" accept="image/gif,image/jpeg,image/jpg,image/png,image/svg">
js代码:
//点击按钮上传图片之后
$("#file").on('change', function () {
new Promise(function (resolve) {
printImgUp(resolve, this);
}.bind(this)).then((result) => {
$(this).val("");
//上传图片解析的结果
result != null ? alert(result) : alert('识别失败');
});
});
//createObjectURL兼容性处理,返回file对象的URL
function getObjectURL(file) {
var url = null;
if (window.createObjectURL != undefined) { // basic
url = window.createObjectURL(file);
} else if (window.URL != undefined) { // mozilla(firefox)
url = window.URL.createObjectURL(file);
} else if (window.webkitURL != undefined) { // webkit or chrome
url = window.webkitURL.createObjectURL(file);
}
return url;
}
//上传图片识别二维码
function printImgUp(resolve, fileId) {
//得到图片的URL
var file = getObjectURL(fileId.files[0]);
//解析URL
qrcode.decode(file);
//结果回调
qrcode.callback = function (e) {
URL.revokeObjectURL(file);
resolve(e != "error decoding QR Code" ? e : null);
}
}
实现逻辑:点击file按钮上传图片后,通过new Promise调用printImgUp方法进行解析,在printImgUp方法中能获取到上传图片的URL,然后通过qrcode.decode解析这个URL,最后qrcode.callback会返回解析的结果,比扫描简单多了!!!