前端实现视频流扫描二维码的方法支持跨端,

1,安装依赖 npm i jsqr  

2.


import {  Button,  } from 'antd';
import React, { useState, useEffect, useRef } from 'react';
import jsQR from 'jsqr';

const QRScanner = ({ goNext, setcancel }) => {
    const [isScanning, setIsScanning] = useState(true);
    const [cameraFacing, setCameraFacing] = useState('environment'); // 'user' for front, 'environment' for back
    const videoRef = useRef(null);
    const canvasRef = useRef(null);
    const scanAreaRef = useRef(null);
    useEffect(() => {
        const initCamera = async () => {
            try {
                const stream = await navigator.mediaDevices.getUserMedia({
                    video: { facingMode: cameraFacing },
                    audio: false,
                });
                videoRef.current.srcObject = stream;
                videoRef.current.onloadedmetadata = () => {
                    videoRef.current.play();
                };
            } catch (error) {
                console.error('Error accessing camera:', error);
            }
        };

        initCamera();

        return () => {
            if (videoRef.current && videoRef.current.srcObject) {
                videoRef.current.srcObject.getTracks().forEach(track => track.stop());
            }
        };
    }, [cameraFacing]);

    useEffect(() => {
        const scanningLoop = () => {
            if (!isScanning || !videoRef.current || !canvasRef.current) return;

            const canvas = canvasRef.current;
            const context = canvas.getContext('2d');
            context.drawImage(videoRef.current, 0, 0, canvas.width, canvas.height);
            const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
            const code = jsQR(imageData.data, imageData.width, imageData.height, { inversionAttempts: "dontInvert" });

            if (code && code.data) {
                console.log('Detected QR Code:', code.data);
                const trydd = "E28011700000020A7CFCCFF1";  //测试
                stopCamera()
                setcancel && setcancel(false)
                goNext(code.data)

            }

            requestAnimationFrame(scanningLoop);
        };

        if (isScanning) {
            scanningLoop();
        }

        return () => {
            cancelAnimationFrame(scanningLoop);
        };
    }, [isScanning]);

    const toggleCamera = () => {
        setCameraFacing(cameraFacing === 'user' ? 'environment' : 'user');
        // 重置扫描状态,因为切换摄像头后需要重新开始扫描
        setIsScanning(false);
    };

    const startScan = () => {
        setIsScanning(true);

    };

    const stopScan = () => {
        setIsScanning(false);

    };

    const stopCamera = () => {
        if (videoRef.current && videoRef.current.srcObject) {
            videoRef.current.srcObject.getTracks().forEach(track => track.stop());
            videoRef.current.srcObject = null;
        }
        setstatus(false)
        stopScan()
    };

    useEffect(() => {
        return () => {
            stopCamera();
        };
    }, []);
    const [status, setstatus] = useState(true)
    const kxsxt = () => {
        setCameraFacing(cameraFacing === 'user' ? 'environment' : 'user');
        setstatus(true)
    }
    const gbadd = () => {
        stopCamera()
        setcancel && setcancel(false)
    }
    const scanLineStyle = {
        position: 'absolute',
        width: '100%',
        height: '2px',
        backgroundColor: 'rgb(0, 138, 217)', // 扫描线颜色和透明度
        animation: `scan 5s linear infinite`,
    };
    //     @keyframes scan {
    //       0% {transform: translateY(-26rem);} /* 开始于屏幕上方超出可视区域 */
    // 50% {transform: translateY(100%);} /* 移动到屏幕底部 */
    // 100% {transform: translateY(-25rem);} /* 返回到初始位置,准备下一轮循环 */

    // 关键帧动画定义
    const scanKeyframes = `
        @keyframes scan {
          0% {transform: translateY(-24em);} /* 开始于屏幕上方超出可视区域 */
     50% {transform: translateY(90%);} /* 移动到屏幕底部 */
     100% {transform: translateY(-25rem);} /* 返回到初始位置,准备下一轮循环 */
  }
   
        
      `;
    return (
        <div style={{
            position: "absolute",
            top: "50%",
            left: "50%",
            transform: "translate(-50%, -50%)",
            width: "30rem",
            height: "30rem",
            border: "1px solid",
            background: "white"

        }}>
            <style>{scanKeyframes}</style> {/* 注入关键帧动画定义 */}
            {/* eslint-disable jsx-a11y/media-has-caption */}
            <video ref={videoRef} style={{
                display: 'block', width: "30rem", height: "25rem",
                transform: 'scale(-1, 1)'
            }} />
            <canvas ref={canvasRef} style={{ display: 'none' }} />

            <div id="scanLine" style={{ ...scanLineStyle, display: isScanning ? 'block' : 'none' }}></div> {/* 动态显示/隐藏扫描线 */}
            <div
                ref={scanAreaRef}
                style={{
                    position: 'relative',
                    // width: '300px',
                    // height: '300px',
                    // border: '2px solid #333',
                    display: 'flex',
                    height: "4rem",
                    justifyContent: 'center',
                    alignItems: 'center',
                    // margin: '10%'
                }}
            >
                {/* {!isScanning && !videoRef.current?.srcObject && (
                    <p>摄像头已关闭,点击开始扫描以打开。</p>
                )} */}
                {isScanning ? (
                    <>
                        {/* <PauseCircleOutlined onClick={stopScan} /> */}
                        <Button onClick={stopScan}>停止扫描</Button>
                        {/* <span className={styles['icon-camera-toggle']} onClick={toggleCamera} /> */}
                        <Button onClick={toggleCamera}>切换摄像头</Button>
                    </>
                ) : (

                    // <PlayCircleOutlined onClick={startScan} />
                    <Button onClick={startScan}>开始扫描</Button>
                )}

                {status ? <Button onClick={stopCamera}>关闭摄像头</Button> : <Button onClick={kxsxt}>开启摄像头</Button>}
                <Button onClick={gbadd}>关闭</Button>
            </div>
        </div>
    );
};

export default QRScanner;

3,goNext 这个就是获取扫描二维码字符串回调函数 ,setcancel 关闭当前的扫描组件的函数。

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值