WEB端扫描二维码和条形码[优化版]

之前写过一个demo也是web端扫描条形码和二维码,但是有个问题。程序扫描的是整个可视区域范围不是扫描框,这就会导致如果扫描框也有条码会出错。这次的例子修复了这个问题,扫描区域就是扫描框的区域。而且对不同情况都有良好的兼容,可以把可视区域和扫描框长宽传入组件,组件会进行计算从摄像头中得到扫描框的图像进行识别。

图片1

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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
Vue.component('scanner-box',{
props: {
// 视口长宽
viewportWidth:{
type: Number
},
viewportHeight:{
type: Number
},
// 扫描框长宽
scanWidth:{
type: Number
},
scanHeight:{
type: Number
}
},
data(){
return {
// 摄像头分辨率
cameraWidth: 0,
cameraHeight: 0,
canvas1Width: 0,
canvas1Height: 0
}
},
methods: {
handleImageSize(video,item1,item2){
let videoWidth = this.cameraWidth,
videoHeight = this.cameraHeight,
viewWidth = this.viewportWidth,
viewHeight = this.viewportHeight,
scanW = this.scanWidth,
scanH = this.scanHeight,
w,h;
let context1 = item1.getContext("2d"),
context2 = item2.getContext("2d");
if (viewWidth/viewHeight > videoWidth/videoHeight){
// 视口比大于摄像头分辨率比 => 摄像头高度需要裁剪
w = viewWidth,h = parseInt(w/(videoWidth/videoHeight));
}else if (viewWidth/viewHeight < videoWidth/videoHeight){
// 视口比小于摄像头分辨率比 => 摄像头宽度需要裁剪
h = viewHeight,w = parseInt(h*(videoWidth/videoHeight));
}else {
// 视口比等于摄像头分辨率比 => 摄像头长宽都不需要裁剪
w = viewWidth,h = viewHeight;
}
this.canvas1Width = w;
this.canvas1Height = h;
this.$nextTick(() => {
context1.drawImage(video,0,0,videoWidth,videoHeight,0,0,w,h);
context2.drawImage(item1,(w/2)-(scanW/2),(h/2)-(scanH/2),scanW,scanH,0,0,scanW,scanH);
})

},
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 () {
// 摄像头分辨率
console.log('摄像头分辨率');
console.log(video.videoWidth + 'x' + video.videoHeight);
// 视口分辨率
console.log('视口分辨率');
console.log(window.innerWidth + 'x' + window.innerHeight);
_this.cameraWidth = video.videoWidth;
_this.cameraHeight = video.videoHeight;
// 发送图片进行识别
_this.readImg();
};
},
videoError(error){
console.log("访问用户媒体设备失败:",error.name,error.message);
},
readImg(){
let video = this.$refs.video,
canvas1 = this.$refs.canvas1,
context = canvas1.getContext("2d"),
canvas2 = this.$refs.canvas2,
context2 = canvas2.getContext("2d"),
_this = this;

let timer = setInterval(function () {
_this.handleImageSize(video,canvas1,canvas2);
// 扫码条形码
let imgUri = canvas2.toDataURL();
_this.readBarcode(imgUri,timer);

// 扫码二维码
let imageData = context2.getImageData(0, 0, _this.scanWidth, _this.scanHeight);
_this.readQrcode(imageData.data,timer);
},500)
},
readBarcode(imgBase64,timer){
let _this = this;
Quagga.decodeSingle({
decoder: {
readers: ["code_128_reader"]
},
// locate为true程序会自动寻找条码,找不到不会出结果。会对一些清晰度不高的图片有影响
locate: false,
src: imgBase64
}, function(result){
if (result){
if(result.codeResult){
// 扫描成功后清除定时器,停止扫描
// clearInterval(timer);
_this.$emit('ondata',{ type: 'barcode', result: result.codeResult.code});
}else {
console.log("正在扫条形码...not detected");
}
}else {
console.log("正在扫条形码...not detected");
}
});
},
readQrcode(data,timer){
let _this = this;
let code = jsQR(data, _this.scanWidth, _this.scanHeight, {
// 只支持白底黑码,默认支持白底黑码和黑底白码。
inversionAttempts: "dontInvert",
});

if (code){
// clearInterval(timer);
_this.$emit('ondata',{ type: 'qrcode', result: code.data});
//_this.$refs.audio.play();
}else {
console.log('正在扫二维码...');
}
}
},
mounted(){
// 检测浏览器是否支持getUserMedia
if (navigator.mediaDevices.getUserMedia || navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia){
//调用用户媒体设备,访问摄像头
this.initVideo({
video:{
width: window.innerWidth*2,
facingMode: {
// 强制后置摄像头,pc端做测试请注释掉,不然会报错
// exact: "environment"
}
}
});
} else {
alert("你的浏览器不支持访问用户媒体设备");
}

Common.getOrientationChange(function (res) {
console.log(res);
if (res){
window.location.reload();
}
});
},
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 class="scanner-text">放入框内,自动扫描</div>
</div>
<audio id="audio" ref="audio" src="./css/success.mp3" style="width: 0px;height: 0px"></audio>
<video class="video-view" ref="video" autoplay playsinline="true" webkit-playsinline="true"></video>
<canvas ref="canvas1" :width="canvas1Width" :height="canvas1Height" style="display: none"></canvas>
<canvas ref="canvas2" :width="scanWidth" :height="scanHeight" style="display: none"></canvas>
</div>`
});

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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#app {
/*width: 100%;*/
}
.left, .right {
width: 50%;
float: left;
}
.left {
background: #f7f7f7;
height: 100vh;
}
.stockSearch-title {
background: #fff;
border: 1px solid #dcdfe6;
color: #606266;
font-weight: 600;
height: 40px;
line-height: 40px;
position: relative;
text-align: center;
}
.stockSearch-title a {
color: #79bbff;
font-size: 14px;
position: absolute;
left: 20px;
}
.stockSearch-title .iconLeft-1 {
font-size: 14px;
font-weight: 600;
}
.stockSearch-result {
background: #fff;
box-sizing: border-box;
width: 92%;
min-height: 500px;
margin: 20px auto;
padding: 12px 20px 15px 20px;
}
.stockSearch-result .caption{
color: #4db3a2;
font-weight: 600;
padding: 10px 0;
}
.table .is-waiting{
color: #c0c4cc;
font-size: 16px;
text-align: center;
padding: 20px;
}
.table .hide {
display: none;
}
.table table {
background: #fff;
border: 1px solid #dddddd;
border-spacing: 0;
border-collapse: collapse;
color: #606266;
font-size: 14px;
width:100%;
}
.td-stock-title {
font-weight: 600;
padding: 8px;
text-align: center;
}
.table tr:nth-of-type(5){
color: #428bca;
}
.table td {
border: 1px solid #ddd;
}
.table td >div div {
padding: 4px 8px;
}
.table td >div div:nth-child(odd) {
background: #f9f9f9;
}
.table-num {
/*color: #303133;*/
font-weight: 600;
}
.table-current {
color: #fff;
background: #64b1f1;
font-size: 12px;
padding: .2em .3em;
border-radius: 4px;
}
/* 右侧扫码 */
#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: 50vw;
height: 100vh;
position: relative;
z-index: 88;
border-top: calc((100vh - 30vw)/2) solid rgba(0,0,0,.2);
border-bottom: calc((100vh - 30vw)/2) solid rgba(0,0,0,.2);
border-right: 10vw solid rgba(0,0,0,.2);
border-left: 10vw solid rgba(0,0,0,.2);
}
.scanner-view{
box-sizing: border-box;
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;
/*background: radial-gradient(ellipse,#71e52c,#001800);*/
border-radius: 20px;
z-index: 90;
animation: myScan 1s infinite alternate;
}
@keyframes myScan{
from {
top: 0;
}
to {
top: 30vw;
}
}
.scanner-view-arrow{
position: absolute;
width: 5vw;
height: 5vw;
border: 2px solid #09bb07;
}
.scanner-view-arrow.arrow1{
top: -1px;
left: -1px;
z-index: 99;
border-right: none;
border-bottom: none;
}
.scanner-view-arrow.arrow2{
top: -1px;
right: -1px;
z-index: 99;
border-left: none;
border-bottom: none;
}
.scanner-view-arrow.arrow3{
bottom: -1px;
left: -1px;
z-index: 99;
border-right: none;
border-top: none;
}
.scanner-view-arrow.arrow4{
bottom: -1px;
right: -1px;
z-index: 99;
border-left: none;
border-top: none;
}
.scanner-text {
color: #909399;
font-size: 20px;
height: 40px;
line-height: 40px;
text-align: center;
margin-top: 20px;
}
.video-view{
position: absolute;
width: 50vw;
height: 100vh;
object-fit: cover;
top: 0;
left: 0;
z-index: 80;
}