background
Not long ago, I did a function about obtaining the browser camera and scanning code recognition. In this article, I sorted out the knowledge points and specific code implementations, and organized them into this article.
This article mainly introduces that by using the front-end development technology vue
📷
is adjusted on the browser side, and the code scanning recognition function is performed, and the recognized QR code is jumped or processed by other operations. The content of this article is divided into background introduction, realization effect, technical introduction, code realization, summary and other parts.
Achieve effect
In this example, there are mainly two pages, the home page and the code scan page. The specific implementation effect is shown in the figure below.
- Home page: Click the
SCAN QRCODE
button to enter the scan code page. - Scan code page: When you enter for the first time, or the system prompt box of
obtain camera access permission pops up, click to allow access, the page starts to load the camera data and start to capture the QR code, if the QR code is captured, start the QR code After the analysis is successful, the pop-up window of successful recognition will be loaded.
📸
online experience: https://dragonir.github.io/h5-scan-qrcode
📌
Tip: You need to access it in a vertical screen in a browser with a camera device. For mobile phone horizontal and vertical screen detection go to my other article 16167882b928bb "Front-end knowledge in games" 16167882b928be.
Technology Brief
WebRTC API
the WebRTC (Web Real-Time Communications) is a real-time communication technology , which allows network applications or sites, in without the aid of an intermediary case, establishing between the browser peer (Peer-to-Peer) of Connect to realize the transmission of video stream and (or) audio stream or other arbitrary data.
WebRTC
Peer-to-Peer data sharing and conference calls without installing any plug-ins or third-party software.
Three main interfaces :
MediaStream
: Able to obtain a synchronized stream of video and audio through the device's camera and microphone.RTCPeerConnection
: 06167882b92a39 is aWebRTC
used to build stable and efficient streaming between peers.RTCDataChannel
: Makes a high-throughput, low-latency channel between browsers for the transmission of arbitrary data.
🔗
Go toMDN
in-depth study: WebRTC_API
WebRTC adapter
Although the WebRTC
specification is relatively sound and stable, not all browsers implement all of its functions. Some browsers need WebRTC API
in order to be used normally.
WebRTC
organization provides a WebRTC adapter
github
to solve the compatibility problem WebRTC
on different browsers. This adapter is a JavaScript shim, which allows you to write code as described in the
WebRTC
specification, without writing prefixes or other compatibility solutions WebRTC
🔗
Go toMDN
in-depth study: WebRTC adapter
Core API navigator.mediaDevices.getUserMedia
The webpage calls the camera need to call getUserMedia API
, MediaDevices.getUserMedia()
will prompt the user to give permission to use the media input, the media input will generate a MediaStream
, which contains the track of the requested media type. This stream can contain a video track (from hardware or virtual video sources, such as cameras, video capture devices, and screen sharing services, etc.), an audio track (also from hardware or virtual audio sources, such as microphones, A/D converters, etc.) Etc.), it may also be other track types.
It returns a Promise
object, and after success, it will resolve
back a MediaStream object by 06167882b92d7f; if the user refuses the permission or the required media source is not available,
promise
will reject
back a PermissionDeniedError
or NotFoundError
. (The returned promise object may be neither
resolve
nor reject
, because the user does not have to choose to allow or deny.)
You can usually use navigator.mediaDevices
to get MediaDevices
, for example:
navigator.mediaDevices.getUserMedia(constraints)
.then(function(stream) {
// 使用这个stream
})
.catch(function(err) {
// 处理error
})
🔗
go toMDN
to learn more: navigator.mediaDevices.getUserMedia
QR code analysis library JSQR
jsQR
is a pure JavaScript
QR code parsing library, which reads the original image or camera, and locates, extracts and parses any QR code in it.
If you want to use jsQR
scan the webcam stream, you need ImageData
to extract from the video stream, and then you can pass it to jsQR
.
jsQR
derives a method that accepts 4
parameters, which are decoded image data,
width,
height, and
optional objects to further configure the scanning behavior.
imageData
: format [r0, g0, b0, a0, r1, g1, b1, a1, ...]
of Uint8ClampedArray (bit unsigned integer. 8 fixed array)
rgba
pixel value.
const code = jsQR(imageData, width, height, options);
if (code) {
console.log('找到二维码!', code);
}
🔗
Go togithub
to learn more: jsQR
Code
Process
The entire code scanning process is shown in the following figure: page initialization, first check whether the browser supports mediaDevices
related API
, the browser adjusts the camera, the call fails, the failure callback is executed; the call is successful, the video stream is captured, and then the code scan recognition is performed , Continue to scan without scanning a recognizable QR code. After the code is scanned successfully, a successful scan pattern is drawn and a successful callback is performed.
The following content splits the process and realizes the corresponding functions respectively.
Scan code component Scaner
Page structure
Let's first look at the page structure, which is mainly composed of 4
:
- Prompt box.
- Scan code box.
video
: Show that the camera captures the video stream.canvas
: Draw video frames for QR code recognition.
<template>
<div class="scaner" ref="scaner">
<!-- 提示框:用于在不兼容的浏览器中显示提示语 -->
<div class="banner" v-if="showBanner">
<i class="close_icon" @click="() => showBanner = false"></i>
<p class="text">若当前浏览器无法扫码,请切换其他浏览器尝试</p>
</div>
<!-- 扫码框:显示扫码动画 -->
<div class="cover">
<p class="line"></p>
<span class="square top left"></span>
<span class="square top right"></span>
<span class="square bottom right"></span>
<span class="square bottom left"></span>
<p class="tips">将二维码放入框内,即可自动扫描</p>
</div>
<!-- 视频流显示 -->
<video
v-show="showPlay"
class="source"
ref="video"
:width="videoWH.width"
:height="videoWH.height"
controls
></video>
<canvas v-show="!showPlay" ref="canvas" />
<button v-show="showPlay" @click="run">开始</button>
</div>
</template>
Method: draw
- Draw a line.
- Picture frame (used to draw rectangular graphics after scanning the code successfully).
// 画线
drawLine (begin, end) {
this.canvas.beginPath();
this.canvas.moveTo(begin.x, begin.y);
this.canvas.lineTo(end.x, end.y);
this.canvas.lineWidth = this.lineWidth;
this.canvas.strokeStyle = this.lineColor;
this.canvas.stroke();
},
// 画框
drawBox (location) {
if (this.drawOnfound) {
this.drawLine(location.topLeftCorner, location.topRightCorner);
this.drawLine(location.topRightCorner, location.bottomRightCorner);
this.drawLine(location.bottomRightCorner, location.bottomLeftCorner);
this.drawLine(location.bottomLeftCorner, location.topLeftCorner);
}
},
Method: Initialize
- Check if it is supported.
- Turn up the camera.
- Success and failure processing.
// 初始化
setup () {
// 判断了浏览器是否支持挂载在MediaDevices.getUserMedia()的方法
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
this.previousCode = null;
this.parity = 0;
this.active = true;
this.canvas = this.$refs.canvas.getContext("2d");
// 获取摄像头模式,默认设置是后置摄像头
const facingMode = this.useBackCamera ? { exact: 'environment' } : 'user';
// 摄像头视频处理
const handleSuccess = stream => {
if (this.$refs.video.srcObject !== undefined) {
this.$refs.video.srcObject = stream;
} else if (window.videoEl.mozSrcObject !== undefined) {
this.$refs.video.mozSrcObject = stream;
} else if (window.URL.createObjectURL) {
this.$refs.video.src = window.URL.createObjectURL(stream);
} else if (window.webkitURL) {
this.$refs.video.src = window.webkitURL.createObjectURL(stream);
} else {
this.$refs.video.src = stream;
}
// 不希望用户来拖动进度条的话,可以直接使用playsinline属性,webkit-playsinline属性
this.$refs.video.playsInline = true;
const playPromise = this.$refs.video.play();
playPromise.catch(() => (this.showPlay = true));
// 视频开始播放时进行周期性扫码识别
playPromise.then(this.run);
};
// 捕获视频流
navigator.mediaDevices
.getUserMedia({ video: { facingMode } })
.then(handleSuccess)
.catch(() => {
navigator.mediaDevices
.getUserMedia({ video: true })
.then(handleSuccess)
.catch(error => {
this.$emit("error-captured", error);
});
});
}
},
Method: Periodic scanning
run () {
if (this.active) {
// 浏览器在下次重绘前循环调用扫码方法
requestAnimationFrame(this.tick);
}
},
Method: success callback
// 二维码识别成功事件处理
found (code) {
if (this.previousCode !== code) {
this.previousCode = code;
} else if (this.previousCode === code) {
this.parity += 1;
}
if (this.parity > 2) {
this.active = this.stopOnScanned ? false : true;
this.parity = 0;
this.$emit("code-scanned", code);
}
},
Method: stop
// 完全停止
fullStop () {
if (this.$refs.video && this.$refs.video.srcObject) {
// 停止视频流序列轨道
this.$refs.video.srcObject.getTracks().forEach(t => t.stop());
}
}
Method: Scan
- Draw the video frame.
- Scan code recognition.
// 周期性扫码识别
tick () {
// 视频处于准备阶段,并且已经加载足够的数据
if (this.$refs.video && this.$refs.video.readyState === this.$refs.video.HAVE_ENOUGH_DATA) {
// 开始在画布上绘制视频
this.$refs.canvas.height = this.videoWH.height;
this.$refs.canvas.width = this.videoWH.width;
this.canvas.drawImage(this.$refs.video, 0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
// getImageData() 复制画布上制定矩形的像素数据
const imageData = this.canvas.getImageData(0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
let code = false;
try {
// 识别二维码
code = jsQR(imageData.data, imageData.width, imageData.height);
} catch (e) {
console.error(e);
}
// 如果识别出二维码,绘制矩形框
if (code) {
this.drawBox(code.location);
// 识别成功事件处理
this.found(code.data);
}
}
this.run();
},
Parent component
Scaner
mainly loads the page and displays the callback of the scanning result of Scaner
Page structure
<template>
<div class="scan">
<!-- 页面导航栏 -->
<div class="nav">
<a class="close" @click="() => $router.go(-1)"></a>
<p class="title">Scan QRcode</p>
</div>
<div class="scroll-container">
<!-- 扫码子组件 -->
<Scaner
v-on:code-scanned="codeScanned"
v-on:error-captured="errorCaptured"
:stop-on-scanned="true"
:draw-on-found="true"
:responsive="false"
/>
</div>
</div>
</template>
Parent component method
import Scaner from '../components/Scaner';
export default {
name: 'Scan',
components: {
Scaner
},
data () {
return {
errorMessage: "",
scanned: ""
}
},
methods: {
codeScanned(code) {
this.scanned = code;
setTimeout(() => {
alert(`扫码解析成功: ${code}`);
}, 200)
},
errorCaptured(error) {
switch (error.name) {
case "NotAllowedError":
this.errorMessage = "Camera permission denied.";
break;
case "NotFoundError":
this.errorMessage = "There is no connected camera.";
break;
case "NotSupportedError":
this.errorMessage =
"Seems like this page is served in non-secure context.";
break;
case "NotReadableError":
this.errorMessage =
"Couldn't access your camera. Is it already in use?";
break;
case "OverconstrainedError":
this.errorMessage = "Constraints don't match any installed camera.";
break;
default:
this.errorMessage = "UNKNOWN ERROR: " + error.message;
}
console.error(this.errorMessage);
alert('相机调用失败');
}
},
mounted () {
var str = navigator.userAgent.toLowerCase();
var ver = str.match(/cpu iphone os (.*?) like mac os/);
// 经测试 iOS 10.3.3以下系统无法成功调用相机摄像头
if (ver && ver[1].replace(/_/g,".") < '10.3.3') {
alert('相机调用失败');
}
}
Complete code
🔗
github: https://github.com/dragonir/h5-scan-qrcode
Summarize
Application extension
I think the following functions can be achieved by calling the camera and scanning recognition through the browser. What functional applications do you think are very wow, 16167882b93b19 can be achieved by scanning the code on the browser side
😂
?
🌏
link jump.🛒
price inquiry.🔒
login authentication.📂
file download.
compatibility
❗
even withadapter
,getUserMedia API
there are not supported in some browsers.❗
low-version browsers (such asiOS 10.3
below),Android
niche browsers (such asIQOO
own browser) are not compatible.❗
QQ
,WeChat built-in browser cannot be called.
Reference
- [1]. Taking still photos with WebRTC
- [2]. Choosing cameras in JavaScript with the mediaDevices API
- [3]. How to use JavaScript to access the front and rear cameras of the device
address of this article: 16167882b93d5a https://segmentfault.com/a/1190000040809122
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。