vue+zxing 扫描条形码

发布时间 2023-07-18 17:08:58作者: 非想非非想天

背景

扫描甲方商品身上的条形码。

吐槽下:这玩意又细又小,还反光,最后用的uni-app上的插件,而且不用自己封装了。虽然和我之前的二方案差不多,即使用

Quagga,打开video,将每帧画成canvas,然后转换为图片交给Quagga识别,缺点是功耗大,最后实装的表现还行,就是扫几个码,手机热了。

回归正题,最开始用的zxing,虽然后面测试发现ios部分手机没有聚焦,巨拉。但还是记录下怎么做的吧。

最下面是全部代码。

事前准备

安装zxing插件,--save是保存到package.json中, webrtc-adapter也是。

npm install @zxing/library --save
npm install webrtc-adapter --save

解释

zxing 是js插件,webrtc是引用手机设备用的具体查看mdn文档

MediaDevices.getUserMedia() - Web API 接口参考 | MDN (mozilla.org)

中间遇见的问题

zxing创建的video视频清晰度太低,然后用了webrtc创建,但兼容性有问题,查找文章,做了个兼容性判断,然后发现画面太小了。

碎碎念:其实是条形码太小了,还不清晰,颜色太淡。

最后发现在定义video属性的时候加zoom就可调整缩放,然后本人安卓redmK60好使,但ios不行。

参考文章

(66条消息) Vue 扫描二维码、条形码_vue扫一扫条形码功能_aibujin的博客-CSDN博客

代码

<template>
  <div class="scan-page">
    <!-- 扫码区域 -->
    <div class="video-box">
      <video ref="video" class="scan-video" id="video" autoplay></video>
    </div>
    <!-- 扫码样式 -->
    <div class="qr-scanner">
      <div class="box">
        <div class="line"></div>
        <div class="angle"></div>
      </div>
      <div class="back-arrow" @click="goBack">
        <span>返回</span>
      </div>
    </div>
  </div>
</template>
 
<script>
// WebRTC适配器 只需要引入就ok
import 'webrtc-adapter'
import { BrowserMultiFormatReader } from '@zxing/library';

export default {
  name: 'ScanCodePage',  // 扫码页面
  data() {
    return {
      codeReader: null,
      // tipShow: false,  // 是否展示提示
      tipMsg: '',  // 提示文本内容
      scanText: '',  // 扫码结果文本内容
      visible: false, //提示信息显示
      streaming: false, //初始化
      deviceId: null, //摄像头id
      video: null
    }
  },
  // 进入时的初始化
  async mounted() {

    let arr = [];
    this.codeReader = await new BrowserMultiFormatReader();
    navigator.mediaDevices.enumerateDevices()
      .then(function (devices) {
        devices.forEach(function (device) {
          if (device.kind === 'videoinput') {
            arr.push(device.deviceId)
          }
        });
      }).finally(res => {
        this.deviceId = arr[arr.length - 1]; //后置摄像头一般为最后一个,也可以在constraints中定义为后置摄像头,具体参数请查看mdn文档
        this.init();
      })
  },
  // 销毁时的还原
  beforeDestroy() {
    this.codeReader.reset();
    this.codeReader = null;
  },
  methods: {
    init() {  // 初始化摄像头
      this.video = document.querySelector('#video');
      let constraints = {
        video: {
          deviceId: this.deviceId,
          width: { min: 800, ideal: 1600 },
          height: { min: 600, ideal: 1200 },
          advanced: [
            { width: 1200, height: 900 },
            { aspectRatio: 1.33 }
          ],
          zoom: 3 //只有部分手机生效,苹果不生效
        }, audio: false
      }
      this.getUserMedia(constraints, this.getUserMediaSuccess, this.getUserMediaError)

      this.video.addEventListener('canplay', (ev) => {
        if (!this.streaming) {
          this.height = this.video.videoHeight / (this.video.videoWidth / this.width);
          if (isNaN(this.height)) {
            this.height = this.width / (4 / 3);
          }
          this.video.setAttribute('width', this.width);
          this.video.setAttribute('height', this.height);
          this.streaming = true;
        }
      }, false);

      this.decodeFromInputVideoFunc();
    },
    decodeFromInputVideoFunc() {  // 使用摄像头扫描
      this.codeReader.reset(); // 重置
      this.codeReader.decodeFromInputVideoDeviceContinuously(this.deviceId, 'video', (result, err) => {
        this.tipMsg = '正在尝试识别...';
        if (result) {
          console.log('扫码结果', result);
          this.scanText = result.text;
          alert(this.scanText)
          if (this.scanText) {
            this.visible = true
            alert(this.scanText+'==='+this.visible)

            console.log()
          }
        }
        console.log(err)
      }).catch(err => {
        console.log(err)
      });
    },
    // 浏览器适配 这个参考了简书上的文章,找不到原文了
    getUserMedia(constraints, success, error) {
      if (navigator.mediaDevices.getUserMedia) {
        //最新的标准API
        navigator.mediaDevices.getUserMedia(constraints).then(success).catch(error);
      } else if (navigator.webkitGetUserMedia) {
        //webkit核心浏览器
        navigator.webkitGetUserMedia(constraints, success, error)
      } else if (navigator.mozGetUserMedia) {
        //firfox浏览器
        navigator.mozGetUserMedia(constraints, success, error);
      } else if (navigator.getUserMedia) {
        //旧版API
        navigator.getUserMedia(constraints, success, error);
      }
    },
    //获取设备成功的方法
    getUserMediaSuccess(stream) {
      console.log(stream);
    },
    //获取设备失败的重写方法
    getUserMediaError(error) {
      console.log(`访问用户媒体设备失败${error.name}, ${error.message}`);
    },
    goBack() {  // 返回上一页
      this.$destroy();
      this.$router.go(-1);
    },
  }
}
</script>
 
<style lang="scss" scoped >

.video-box {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 90%;
  height: 20%;


}

.scan-video {
  width: 100%;
  height: 100%;
  object-fit: cover;

}

.scan-page {
  min-height: 100vh;
  background-color: #363636;




  .scan-tip {
    width: 100vw;
    position: fixed;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    text-align: center;
    color: white;
    font-size: 5vw;
  }

  .qr-scanner {
    background-image: linear-gradient(0deg,
        transparent 24%,
        rgba(32, 255, 77, 0.1) 25%,
        rgba(32, 255, 77, 0.1) 26%,
        transparent 27%,
        transparent 74%,
        rgba(32, 255, 77, 0.1) 75%,
        rgba(32, 255, 77, 0.1) 76%,
        transparent 77%,
        transparent),
      linear-gradient(90deg,
        transparent 24%,
        rgba(32, 255, 77, 0.1) 25%,
        rgba(32, 255, 77, 0.1) 26%,
        transparent 27%,
        transparent 74%,
        rgba(32, 255, 77, 0.1) 75%,
        rgba(32, 255, 77, 0.1) 76%,
        transparent 77%,
        transparent);
    background-size: 0.3rem 0.3rem;
    background-position: -1rem -1rem;

    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 9;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.5);
  }

  .qr-scanner .box {
    width: 90%;
    height: 20%;
    max-height: 75vh;
    max-width: 75vh;
    position: relative;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    overflow: hidden;
    border: 0.02rem solid rgba(0, 255, 51, 0.2);
  }

  .qr-scanner .line {
    height: calc(100% - 2px);
    width: 100%;
    background: linear-gradient(180deg, rgba(0, 255, 51, 0) 43%, #00ff33 211%);
    border-bottom: 3px solid #00ff33;
    transform: translateY(-100%);
    animation: radar-beam 2s infinite;
    animation-timing-function: cubic-bezier(0.53, 0, 0.43, 0.99);
    animation-delay: 1.4s;
  }

  .qr-scanner .box:after,
  .qr-scanner .box:before,
  .qr-scanner .angle:after,
  .qr-scanner .angle:before {
    content: '';
    display: block;
    position: absolute;
    width: 3vw;
    height: 3vw;
    border: 0.02rem solid transparent;
  }

  .qr-scanner .box:after,
  .qr-scanner .box:before {
    top: 0;
    border-top-color: #00ff33;
  }

  .qr-scanner .angle:after,
  .qr-scanner .angle:before {
    bottom: 0;
    border-bottom-color: #00ff33;
  }

  .qr-scanner .box:before,
  .qr-scanner .angle:before {
    left: 0;
    border-left-color: #00ff33;
  }

  .qr-scanner .box:after,
  .qr-scanner .angle:after {
    right: 0;
    border-right-color: #00ff33;
  }

  @keyframes radar-beam {
    0% {
      transform: translateY(-100%);
    }

    100% {
      transform: translateY(0);
    }
  }

  .back-arrow {
    position: fixed;
    top: 10px;
    left: 10px;
    width: 100px;
    height: 50px;
    // border-radius: 100%;
    // background-color: rgba(0, 0, 0, 0.3);
    font-size: 20px;
    z-index: 999;

    display: flex;
    justify-content: center;
    align-items: center;
    color: #fff;
  }

}
</style>