好久没有写博客,最近刚做完react 调用手机摄像头识别二维码的功能,做的过程中遇到了好多问题,也百度了很多方法结合实际情况最终实现了此功能。
在实现的过程中有很多都是在原声的HTML和JS里实现的,拿过来改改就行了。
首先安装组件,组件文档地址
npm i jsqr --save
看文档发现jsqr 使用很简单,只需要传递三个参数就可以使用了。但是难就难在这三个参数怎么获取,通过查看jsqr的事例发现是结合navigator.mediaDevices.getUserMedia实现的
mediaDevices.getUserMedia文档地址
通过mediaDevices.getUserMedia方法调起录像功能,把视频赋给video,在截取视频中的某一帧传给jsqr进行解析,如果解析出二维码则返回解析数据,没有的话就继续截取 表面意思是这个意思,我理解的不够深,若有不正确的地方希望大佬批评指正
关于mediaDevices.getUserMedia的使用建议使用MDN提供的代码,一下代码是复制于MDN
// 老的浏览器可能根本没有实现 mediaDevices,所以我们可以先设置一个空的对象
if (navigator.mediaDevices === undefined) {
navigator.mediaDevices = {};
}
// 一些浏览器部分支持 mediaDevices。我们不能直接给对象设置 getUserMedia
// 因为这样可能会覆盖已有的属性。这里我们只会在没有getUserMedia属性的时候添加它。
if (navigator.mediaDevices.getUserMedia === undefined) {
navigator.mediaDevices.getUserMedia = function(constraints) {
// 首先,如果有getUserMedia的话,就获得它
var getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
// 一些浏览器根本没实现它 - 那么就返回一个error到promise的reject来保持一个统一的接口
if (!getUserMedia) {
return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
}
// 否则,为老的navigator.getUserMedia方法包裹一个Promise
return new Promise(function(resolve, reject) {
getUserMedia.call(navigator, constraints, resolve, reject);
});
}
}
navigator.mediaDevices.getUserMedia({ audio: true, video: true })
.then(function(stream) {
var video = document.querySelector('video');
// 旧的浏览器可能没有srcObject
if ("srcObject" in video) {
video.srcObject = stream;
} else {
// 防止在新的浏览器里使用它,应为它已经不再支持了
video.src = window.URL.createObjectURL(stream);
}
video.onloadedmetadata = function(e) {
video.play();
};
})
.catch(function(err) {
console.log(err.name + ": " + err.message);
});
以上代码实现了调起摄像头拍摄视频。接下来就要结合jsqr解析视频中出现的二维码,一下是完整代码。其中tick方法是复制于jsqr的事例代码中,有一些代码没有必要的,我这边没有做删除,如果你看着碍眼删掉即可
import React, { PureComponent } from 'react';
import '../index.less';
import jsQR from 'jsqr';
let scanner = null;
let video = null;
let canvasElement = null;
let canvas = null;
let loadingMessage = null;
let outputContainer = null;
let outputMessage = null;
let outputData = null;
export default class scanPage extends PureComponent {
componentDidMount() {
const that = this;
video = document.createElement('video');
canvasElement = document.getElementById('canvas');
canvas = canvasElement.getContext('2d');
loadingMessage = document.getElementById('loadingMessage');
outputContainer = document.getElementById('output');
outputMessage = document.getElementById('outputMessage');
outputData = document.getElementById('outputData');
console.log(navigator.mediaDevices )
// 老的浏览器可能根本没有实现 mediaDevices,所以我们可以先设置一个空的对象
if (navigator.mediaDevices === undefined) {
navigator.mediaDevices = {};
}
// 一些浏览器部分支持 mediaDevices。我们不能直接给对象设置 getUserMedia
// 因为这样可能会覆盖已有的属性。这里我们只会在没有getUserMedia属性的时候添加它。
if (navigator.mediaDevices.getUserMedia === undefined) {
navigator.mediaDevices.getUserMedia = function (constraints) {
// 首先,如果有getUserMedia的话,就获得它
var getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
// 一些浏览器根本没实现它 - 那么就返回一个error到promise的reject来保持一个统一的接口
if (!getUserMedia) {
return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
}
// 否则,为老的navigator.getUserMedia方法包裹一个Promise
return new Promise(function (resolve, reject) {
getUserMedia.call(navigator, constraints, resolve, reject);
});
};
}
navigator.mediaDevices
.getUserMedia({ audio: false, video: { facingMode: "environment" } })
.then(function (stream) {
// 旧的浏览器可能没有srcObject
video.srcObject = stream;
video.setAttribute('playsinline', true); // required to tell iOS safari we don't want fullscreen
video.play();
window.requestAnimationFrame(that.tick);
})
.catch(function (err) {
console.log(err.name + ': ' + err.message);
});
}
handleStartCode = () => {
scanner.start();
};
componentWillUnmount() {}
drawLine = (begin, end, color) => {
canvas.beginPath();
canvas.moveTo(begin.x, begin.y);
canvas.lineTo(end.x, end.y);
canvas.lineWidth = 4;
canvas.strokeStyle = color;
canvas.stroke();
};
tick = () => {
loadingMessage.innerText = '⌛ Loading video...';
if (video.readyState === video.HAVE_ENOUGH_DATA) {
loadingMessage.hidden = true;
canvasElement.hidden = false;
outputContainer.hidden = false;
canvasElement.height = video.videoHeight;
canvasElement.width = video.videoWidth;
canvas.drawImage(video, 0, 0, canvasElement.width, canvasElement.height);
var imageData = canvas.getImageData(0, 0, canvasElement.width, canvasElement.height);
var code = jsQR(imageData.data, imageData.width, imageData.height, {
inversionAttempts: 'dontInvert',
});
if (code && code.data) {
console.log(code);
this.drawLine(code.location.topLeftCorner, code.location.topRightCorner, '#FF3B58');
this.drawLine(code.location.topRightCorner, code.location.bottomRightCorner, '#FF3B58');
this.drawLine(code.location.bottomRightCorner, code.location.bottomLeftCorner, '#FF3B58');
this.drawLine(code.location.bottomLeftCorner, code.location.topLeftCorner, '#FF3B58');
outputMessage.hidden = true;
outputData.parentElement.hidden = false;
outputData.innerText = code.data;
this.props.history.replace(
`/client-mng/individual-business-mng/list/detail/${code.data}-selfEERegister-scan`
);
} else {
outputMessage.hidden = false;
outputData.parentElement.hidden = true;
}
}
window.requestAnimationFrame(this.tick);
};
render() {
return (
<div>
<div id="loadingMessage">
🎥 Unable to access video stream (please make sure you have a webcam enabled)
</div>
<canvas id="canvas" hidden></canvas>
<div id="output" hidden>
<div id="outputMessage">No QR code detected.</div>
<div hidden>
<b>Data:</b> <span id="outputData"></span>
</div>
</div>
</div>
);
}
}
需要注意的点:
- 调起摄像头必须在https的协议下,否则会不起作用
- navigator.mediaDevices .getUserMedia({ audio: false, video: { facingMode: "environment" } }),这一段代码的意思是禁用录音功能,facingMode: "environment" 是强制使用后置摄像头。