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

图片1

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>`
});
#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;
}