H5端获取摄像头进行扫码(条形码/二维码)

getUserMedia了解

HTML5的getUserMedia API为用户提供访问硬件设备媒体(摄像头、麦克风)的接口,基于该接口,开发者可以在不依赖任何浏览器插件的条件下访问硬件媒体设备。
点击查看getUserMedia的api

代码

Vue.js版

HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div id="scanner">
<div class="model">
<div class="scanner-view">
<div class="scanner-view-arrow arrow1"></div>
<div class="scanner-view-arrow arrow2"></div>
<div class="scanner-view-arrow arrow3"></div>
<div class="scanner-view-arrow arrow4"></div>
<div class="scanner-line"></div>
</div>
</div>
<video class="video-view" ref="video" autoplay playsinline="true" webkit-playsinline="true"></video>
<canvas ref="canvas" width="478" height="850" style="display: none"></canvas>
</div>
</template>

JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
<script>
import jsQR from "jsqr";
import Quagga from "quagga";
export default {
name: '',
data() {
return {
cameraWidth: 0,
cameraHeight: 0
}
},
methods: {
initVideo(constrains){
let _this = this;
if(navigator.mediaDevices.getUserMedia){
//最新标准API
navigator.mediaDevices.getUserMedia(constrains).then(_this.videoSuccess).catch(_this.videoError);
} else if (navigator.webkitGetUserMedia){
//webkit内核浏览器
navigator.webkitGetUserMedia(constrains).then(_this.videoSuccess).catch(_this.videoError);
} else if (navigator.mozGetUserMedia){
//Firefox浏览器
navagator.mozGetUserMedia(constrains).then(_this.videoSuccess).catch(_this.videoError);
} else if (navigator.getUserMedia){
//旧版API
navigator.getUserMedia(constrains).then(_this.videoSuccess).catch(_this.videoError);
}
},
videoSuccess(stream){
let video = this.$refs.video,
_this = this;
//将视频流设置为video元素的源
video.srcObject = stream;
//播放视频
video.play();

video.oncanplay = function () {
// 摄像头分辨率,手机480x640
console.log('摄像头分辨率');
console.log(video.videoWidth,video.videoHeight);
_this.cameraWidth = video.videoWidth;
_this.cameraHeight = video.videoHeight;
// 发送图片进行识别
_this.readImg();
};
},
videoError(error){
console.log("访问用户媒体设备失败:",error.name,error.message);
},
readImg(){
let video = this.$refs.video,
canvas = this.$refs.canvas,
context = canvas.getContext("2d"),
_this = this;
let timer = setInterval(function () {
context.drawImage(video,0,0,_this.cameraWidth,_this.cameraHeight,0,0,478,850);
// 扫码条形码
let imgUri = canvas.toDataURL();
_this.readBarcode(imgUri,timer);

// 扫码二维码
let imageData = context.getImageData(0, 0, 478, 850);
_this.readQrcode(imageData.data,timer);
},1000)
},
readBarcode(imgBase64,timer){
let _this = this;
Quagga.decodeSingle({
inputStream: {
size: 1920
},
locator: {
patchSize: "medium",
halfSample: false
},
decoder: {
readers: [{
format: "code_128_reader",
config: {}
}]
},
locate: true,
src: imgBase64
}, function(result){
if (result){
if(result.codeResult) {
console.log(result.codeResult);
clearInterval(timer);
_this.$emit('ondata',result.codeResult.code);
// alert("扫码成功,结果是..."+result.codeResult.code);

} else {
console.log("正在扫条形码...not detected");
}
}else {
console.log("正在扫条形码...not detected");
}

});
},
readQrcode(data,timer){
let _this = this;
let code = jsQR(data, 478, 850, {
inversionAttempts: "dontInvert",
});

if (code){
clearInterval(timer);
_this.$emit('ondata',code.data);
// alert('扫码成功,结果是...' + code.data);
}else {
console.log('正在扫二维码...');
}
}
},
mounted(){
if (navigator.mediaDevices.getUserMedia || navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia){
//调用用户媒体设备,访问摄像头
this.initVideo({
video:{
height: 800,
facingMode: {
// 强制后置摄像头
exact: "environment"
}
}
});
} else {
alert("你的浏览器不支持访问用户媒体设备");
}
}
}
</script>

CSS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<style scoped>
#scanner {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
position: relative;
}
.model{
box-sizing: border-box;
width: 100vw;
height: 100vh;
position: relative;
z-index: 88;
border-top: calc((100vh - 60vw)/2) solid rgba(0,0,0,.2);
border-bottom: calc((100vh - 60vw)/2) solid rgba(0,0,0,.2);
border-right: 20vw solid rgba(0,0,0,.2);
border-left: 20vw solid rgba(0,0,0,.2);
}
.scanner-view{
width: 100%;
height: 100%;
position: relative;
border: 1px solid rgba(255,255,255,.3);
z-index: 89;
}
.scanner-line{
position: absolute;
width: 100%;
height: 1px;
background: #49FF46;
border-radius: 20px;
z-index: 90;
animation: myScan 1s infinite alternate;
}
@keyframes myScan{
from {
top: 0;
}
to {
top: 34vh;
}
}
.scanner-view-arrow{
position: absolute;
width: 5vw;
height: 5vw;
border: 2px solid #09bb07;
}
.scanner-view-arrow.arrow1{
top: -1px;
left: 0px;
z-index: 99;
border-right: none;
border-bottom: none;
}
.scanner-view-arrow.arrow2{
top: -1px;
right: 0px;
z-index: 99;
border-left: none;
border-bottom: none;
}
.scanner-view-arrow.arrow3{
bottom: -1px;
left: 0px;
z-index: 99;
border-right: none;
border-top: none;
}
.scanner-view-arrow.arrow4{
bottom: -1px;
right: 0px;
z-index: 99;
border-left: none;
border-top: none;
}
.video-view{
position: absolute;
width: 100vw;
height: 100vh;
object-fit: cover;
top: 0px;
left: 0px;
z-index: 80;
}
</style>

注意事项

  • video标签里面的视频会用黑边,可以在video标签的css中加入 object-fit: cover;

  • ios中会出现点击视频全屏情况,可以在video标签中加入 playsinline="true" webkit-playsinline="true"

  • 示例里面用了ElementUI的弹窗组件