react 调用手机摄像头识别二维码(pc端,移动端均可)

好久没有写博客,最近刚做完react 调用手机摄像头识别二维码的功能,做的过程中遇到了好多问题,也百度了很多方法结合实际情况最终实现了此功能。

在实现的过程中有很多都是在原声的HTML和JS里实现的,拿过来改改就行了。

首先安装组件,组件文档地址

npm i jsqr --save

看文档发现jsqr 使用很简单,只需要传递三个参数就可以使用了。但是难就难在这三个参数怎么获取,通过查看jsqr的事例发现是结合navigator.mediaDevices.getUserMedia实现的

mediaDevices.getUserMedia文档地址

通过mediaDevices.getUserMedia方法调起录像功能,把视频赋给video,在截取视频中的某一帧传给jsqr进行解析,如果解析出二维码则返回解析数据,没有的话就继续截取
表面意思是这个意思,我理解的不够深,若有不正确的地方希望大佬批评指正

关于mediaDevices.getUserMedia的使用建议使用MDN提供的代码,一下代码是复制于MDN

// 老的浏览器可能根本没有实现 mediaDevices,所以我们可以先设置一个空的对象

if (navigator.mediaDevices === undefined) {
  navigator.mediaDevices = {};
}

// 一些浏览器部分支持 mediaDevices。我们不能直接给对象设置 getUserMedia
// 因为这样可能会覆盖已有的属性。这里我们只会在没有getUserMedia属性的时候添加它。
if (navigator.mediaDevices.getUserMedia === undefined) {
  navigator.mediaDevices.getUserMedia = function(constraints) {

    // 首先,如果有getUserMedia的话,就获得它
    var getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;

    // 一些浏览器根本没实现它 - 那么就返回一个error到promise的reject来保持一个统一的接口
    if (!getUserMedia) {
      return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
    }

    // 否则,为老的navigator.getUserMedia方法包裹一个Promise
    return new Promise(function(resolve, reject) {
      getUserMedia.call(navigator, constraints, resolve, reject);
    });
  }
}

navigator.mediaDevices.getUserMedia({ audio: true, video: true })
.then(function(stream) {
  var video = document.querySelector('video');
  // 旧的浏览器可能没有srcObject
  if ("srcObject" in video) {
    video.srcObject = stream;
  } else {
    // 防止在新的浏览器里使用它,应为它已经不再支持了
    video.src = window.URL.createObjectURL(stream);
  }
  video.onloadedmetadata = function(e) {
    video.play();
  };
})
.catch(function(err) {
  console.log(err.name + ": " + err.message);
});

以上代码实现了调起摄像头拍摄视频。接下来就要结合jsqr解析视频中出现的二维码,一下是完整代码。其中tick方法是复制于jsqr的事例代码中,有一些代码没有必要的,我这边没有做删除,如果你看着碍眼删掉即可

import React, { PureComponent } from 'react';
import '../index.less';
import jsQR from 'jsqr';

let scanner = null;

let video = null;
let canvasElement = null;
let canvas = null;
let loadingMessage = null;
let outputContainer = null;
let outputMessage = null;
let outputData = null;

export default class scanPage extends PureComponent {
  componentDidMount() {
    const that = this;
    video = document.createElement('video');
    canvasElement = document.getElementById('canvas');
    canvas = canvasElement.getContext('2d');
    loadingMessage = document.getElementById('loadingMessage');
    outputContainer = document.getElementById('output');
    outputMessage = document.getElementById('outputMessage');
    outputData = document.getElementById('outputData');

    console.log(navigator.mediaDevices )

    // 老的浏览器可能根本没有实现 mediaDevices,所以我们可以先设置一个空的对象
    if (navigator.mediaDevices === undefined) {
      navigator.mediaDevices = {};
    }

    // 一些浏览器部分支持 mediaDevices。我们不能直接给对象设置 getUserMedia
    // 因为这样可能会覆盖已有的属性。这里我们只会在没有getUserMedia属性的时候添加它。
    if (navigator.mediaDevices.getUserMedia === undefined) {
      navigator.mediaDevices.getUserMedia = function (constraints) {
        // 首先,如果有getUserMedia的话,就获得它
        var getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;

        // 一些浏览器根本没实现它 - 那么就返回一个error到promise的reject来保持一个统一的接口
        if (!getUserMedia) {
          return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
        }

        // 否则,为老的navigator.getUserMedia方法包裹一个Promise
        return new Promise(function (resolve, reject) {
          getUserMedia.call(navigator, constraints, resolve, reject);
        });
      };
    }

    navigator.mediaDevices
      .getUserMedia({ audio: false, video: { facingMode: "environment" } })
      .then(function (stream) {
        // 旧的浏览器可能没有srcObject
        video.srcObject = stream;
        video.setAttribute('playsinline', true); // required to tell iOS safari we don't want fullscreen
        video.play();
        window.requestAnimationFrame(that.tick);
      })
      .catch(function (err) {
        console.log(err.name + ': ' + err.message);
      });
  }

  handleStartCode = () => {
    scanner.start();
  };

  componentWillUnmount() {}

  drawLine = (begin, end, color) => {
    canvas.beginPath();
    canvas.moveTo(begin.x, begin.y);
    canvas.lineTo(end.x, end.y);
    canvas.lineWidth = 4;
    canvas.strokeStyle = color;
    canvas.stroke();
  };

  tick = () => {
    loadingMessage.innerText = '⌛ Loading video...';
    if (video.readyState === video.HAVE_ENOUGH_DATA) {
      loadingMessage.hidden = true;
      canvasElement.hidden = false;
      outputContainer.hidden = false;

      canvasElement.height = video.videoHeight;
      canvasElement.width = video.videoWidth;
      canvas.drawImage(video, 0, 0, canvasElement.width, canvasElement.height);
      var imageData = canvas.getImageData(0, 0, canvasElement.width, canvasElement.height);
      var code = jsQR(imageData.data, imageData.width, imageData.height, {
        inversionAttempts: 'dontInvert',
      });
      if (code && code.data) {
        console.log(code);
        this.drawLine(code.location.topLeftCorner, code.location.topRightCorner, '#FF3B58');
        this.drawLine(code.location.topRightCorner, code.location.bottomRightCorner, '#FF3B58');
        this.drawLine(code.location.bottomRightCorner, code.location.bottomLeftCorner, '#FF3B58');
        this.drawLine(code.location.bottomLeftCorner, code.location.topLeftCorner, '#FF3B58');
        outputMessage.hidden = true;
        outputData.parentElement.hidden = false;
        outputData.innerText = code.data;
        this.props.history.replace(
          `/client-mng/individual-business-mng/list/detail/${code.data}-selfEERegister-scan`
        );
      } else {
        outputMessage.hidden = false;
        outputData.parentElement.hidden = true;
      }
    }
    window.requestAnimationFrame(this.tick);
  };

  render() {
    return (
      <div>
        <div id="loadingMessage">
          🎥 Unable to access video stream (please make sure you have a webcam enabled)
        </div>
        <canvas id="canvas" hidden></canvas>
        <div id="output" hidden>
          <div id="outputMessage">No QR code detected.</div>
          <div hidden>
            <b>Data:</b> <span id="outputData"></span>
          </div>
        </div>
      </div>
    );
  }
}

需要注意的点:

  1. 调起摄像头必须在https的协议下,否则会不起作用
  2. navigator.mediaDevices .getUserMedia({ audio: false, video: { facingMode: "environment" } }),这一段代码的意思是禁用录音功能facingMode: "environment" 是强制使用后置摄像头。
  • 1
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

琞、小菜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值