/*
* @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谷歌浏览器配置