WEB端调用摄像头识别QR原理及注意事项

/*
 * @Author: yangzihua
 * @Date: 2022-07-01 08:56:22
 * @LastEditors: yangzihua
 * @LastEditTime: 2022-07-04 11:41:40
 * @Description:QR
 */
import React, { memo, useEffect, useRef, useState } from "react";
import { Button, Modal, List } from "antd";
import jsQR from "jsqr";
import { FormLayoutWrapper } from "@/components/Layout/FormLayout";

const CameraComponent = () => {
  const cameraVideoRef = useRef(null);
  const cameraCanvasRef = useRef(null);
  const [modalVisible, setModalVisible] = useState(false);
  const [qrInfo, setQrInfo] = useState([]);

  const handleClose = () => {
    setModalVisible(false);
  };

  // 启动摄像头
  const openMedia = () => {
    const opt = {
      video: {
        width: 550,
        height: 450,
        facingMode: "environment", // 移动端调用后置摄像头
      },
    };
    navigator.mediaDevices.getUserMedia(opt).then(successFunc).catch(errorFunc);
  };

  useEffect(() => {
    openMedia();
  }, []);

  const tick = () => {
    const video = cameraVideoRef.current;
    const canvas = cameraCanvasRef.current;
    const ctx = canvas.getContext("2d");
    if (video.readyState === video.HAVE_ENOUGH_DATA) {
      ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight); // 把视频中的一帧在canvas画布里面绘制出来
      const imageData = ctx.getImageData(
        0,
        0,
        video.videoWidth,
        video.videoHeight
      );
      const code = jsQR(imageData.data, imageData.width, imageData.height, {
        inversionAttempts: "dontInvert",
      });
      if (code && code.data) {
        setModalVisible(true);
        setQrInfo([...qrInfo, code.data]);
        // 扫码一次就跳出
        return;
      } else {
        console.log("扫码中。。。。。。。");
      }
    }
    window.requestAnimationFrame(tick);
  };

  function successFunc(mediaStream) {
    const video = cameraVideoRef.current;
    // 旧的浏览器可能没有srcObject
    if ("srcObject" in video) {
      video.srcObject = mediaStream;
    }
    video.onloadedmetadata = () => {
      video.play();
    };
    tick();
  }

  function errorFunc(err) {
    console.log(`${err.name}: ${err.message}`);
  }

  const handleContinue = () => {
    tick();
  };

  return (
    <div>
      <video
        id="cameraVideo"
        ref={cameraVideoRef}
        style={{
          width: "550px",
          height: "450px",
        }}
      />
      <canvas
        id="cameraCanvas"
        ref={cameraCanvasRef}
        width={550}
        height={450}
        hidden
      />
      <Modal
        visible={modalVisible}
        onCancel={handleClose}
        destroyOnClose
        footer={false}
        title="二维码信息"
      >
        <List
          bordered
          dataSource={qrInfo}
          renderItem={(item) => <List.Item>{item}</List.Item>}
        />
      </Modal>
      <Button onClick={handleContinue}>继续扫码</Button>
    </div>
  );
};

export default CameraComponent |> memo |> FormLayoutWrapper("摄像头扫码");

摄像头识别QR原理

navigator.mediaDevices.getUserMedia(opt).then(successFunc).catch(errorFunc);使用这个API实现。通过navigator.mediaDevices.getUserMedia()方法调起录像功能,把视频赋给video标签,再截取视频中的某一帧通过canvas标签绘制出来传给jsqr进行解析。mediaDevices.getUserMedia文档地址

Jsqr

import jsQR from 'jsqr';

参数

  • width - 要解码的图像的宽度。

  • height - 要解码的图像的高度。

  • options (可选) - 附加选项。

  • inversionAttempts

- ( attemptBoth(默认), dontInvert,onlyInvert 或 invertFirst)

- 应 jsQR 试图反转图像找到与黑色背景而不是白色背景上的黑色模块白色模块 QR 码。此选项默认 attemptBoth 为向后兼容性但会导致性能下降约 50%,并且可能 dontInvert 在将来的版本中默认为。jsqr文档地址

通过MediaDevices.getUserMedia() 获取用户多媒体权限时,需要注意其只工作于以下三种环境:

  • localhost 域

  • 开启了 HTTPS 的域

  • 使用 file:/// 协议打开的本地文件

对应的解决方案:

  • 创建HTTPS服务器,用HTTPS协议的方式发送请求。

  • 在HTTP服务器上,可设置Chrome 的相应参数:在chrome浏览器的地址栏中输入: chrome://flags/#unsafely-treat-insecure-origin-as-secure,将该 flag 切换成 enable 状态;在输入框中填写需要开启的域名或地址,如果有多个,则以逗号分隔;重启浏览器后生效。

部分代码

// 启动摄像头

const openMedia = () => {

const opt = {

video: {

width: 550,

height: 450,

facingMode: 'environment', // 移动端调用后置摄像头

},

};

navigator.mediaDevices.getUserMedia(opt).then(successFunc).catch(errorFunc);

};

// 调起录像功能成功时调用

function successFunc(mediaStream) {

const video = cameraVideoRef.current;

// 旧的浏览器可能没有srcObject

if ('srcObject' in video) {

video.srcObject = mediaStream;

}

video.onloadedmetadata = () => {

video.play();

};

tick();

}

const tick = () => {

const video = cameraVideoRef.current;

const canvas = cameraCanvasRef.current;

const ctx = canvas.getContext('2d');

if (video.readyState ===video.HAVE_ENOUGH_DATA) {

ctx.drawImage(video, 0, 0,video.videoWidth, video.videoHeight); // 把视频中的一帧在canvas画布里面绘制出来

const imageData = ctx.getImageData(0, 0,video.videoWidth, video.videoHeight);

const code = jsQR(imageData.data,imageData.width, imageData.height, {

inversionAttempts: 'dontInvert',

});

if (code && code.data) {

setModalVisible(true);

setQrInfo([...qrInfo,code.data]);

// 扫码一次就跳出

return;

} else {

console.log('扫码中。。。。。。。');

}

}

window.requestAnimationFrame(tick);

};

// 调起录像功能失败时调用

function errorFunc(err) {1

console.log(`${err.name}: ${err.message}`);

}

// 页面DOM

return (

<div>

<video

id="cameraVideo"

ref={cameraVideoRef}

style={{

width: '550px',

height: '450px',

}}

/>

<canvas id="cameraCanvas"ref={cameraCanvasRef} width={550} height={450} hidden />

<Modal

visible={modalVisible}

onCancel={handleClose}

destroyOnClose

footer={false}

title="二维码信息"

>

<List bordered dataSource={qrInfo}renderItem={item => <List.Item>{item}</List.Item>} />

</Modal>

<Button onClick={handleContinue}>继续扫码</Button>

</div>

);

};

附件

平板谷歌浏览器配置

PC谷歌浏览器配置

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值