React.js 애플리케이션에서 바코드를 스캔하는 방법

Cover: "someone scanning a barcode of a package with their smartphone, caricature" – DALL-E mini



편집: 이제 이것을 react-zxing 으로 새 NPM 패키지에 게시했습니다.

배경



이것은 나의 첫 번째 기사가 될 것입니다. 구현하기가 특히 어려웠고 훌륭한 자습서를 찾을 수 없었고 내 reddit post가 약간의 관심을 끌었기 때문에 이 글을 작성하게 되었습니다.

우리는 무엇을 만들고 있습니까?



사이드 프로젝트에 바코드 스캐너를 사용하고 싶었습니다Snxbox . 내 기준은 다음과 같습니다.
  • 사용자가 카메라를 겨냥하고 있는 대상을 볼 수 있도록 사용자의 장치 카메라 출력을 비디오 요소로 스트리밍합니다.
  • 스트림에서 QR 및 EAN 코드를 정확하게 감지하고 결과를 내보냅니다.

  • 대안



    사용할 수 있는 React 호환 패키지를 찾기 시작했습니다. 내가 찾은 즉각적인 패키지는 react-qr-barcode-scanner 간단한 드롭인 반응 구성 요소를 제공했습니다.

    반응 qr-바코드 스캐너


    react-qr-barcode-scanner는 바코드를 디코딩하기 위해 zxing에 의존합니다. EAN 코드를 읽음으로써 일관되지 않은 결과로 인해 발생하는 버그를 발견할 때까지 한동안 사용했습니다. issue on zxing을 찾았는데 수정된 것 같습니다. 그러나 react-qr-barcode-scanner는 여전히 문제가 있는 zxing의 이전 버전을 사용했습니다.

    쿼카2



    이것은 확장하는 또 다른 패키지입니다zxing. React와 함께 사용하는 방법에 대한 예제를 찾았지만 솔직히 it seemed daunting .

    html5-qrcode



    또 다른 패키지 확장zxing . 구현은 a bit easier to follow 이었지만 이것도 zxing 의 이전 버전을 사용하는 것 같아서 사용에 대해 약간 신중했습니다.

    바코드 감지 API 사용



    바코드 스캔을 위한 실험적 API가 있지만 안타깝게도 아직 지원이 제한적인 것 같습니다.

    리팩토링 시도



    결국 종속성을 업데이트하기 위해 react-qr-barcode-scanner를 분기했지만 구현이 quite straight-forward부터 시작되었음을 발견했습니다.

    또한 react-qr-barcode-scanner react-webcam 을 사용하여 zxing에서 디코딩할 스냅샷을 일정 간격으로 찍는 비디오 요소로 카메라를 스트리밍합니다. 실제로 비디오 스트림 자체를 디코딩하지는 않습니다.

    실제로 zxing를 사용하여 비디오 스트림에서 직접 읽고 비디오 요소에서 스트림을 미리 볼 수 있으므로 react-webcam 종속성이 중복됩니다.

    손을 더럽히다



    관찰 결과 대부분의 대안은 디코딩에 zxing를 사용하므로 아마도 안전한 방법일 것입니다.

    따라서 @zxing/library 패키지를 설치합니다. 그런 다음 리더 인스턴스를 만듭니다.

    import { BrowserMultiFormatReader } from '@zxing/library';
    
    const reader = new BrowserMultiFormatReader();
    


    그런 다음 메서드 decodeFromConstraints 를 사용하여 스트림에서 코드를 지속적으로 감지하고 비디오 요소에 표시할 수 있습니다. 첫 번째 인수는 구성 개체, 두 번째 인수는 스트리밍할 동영상 요소, 세 번째 인수는 디코딩 결과를 처리하는 콜백 함수입니다.

    import { BrowserMultiFormatReader } from '@zxing/library';
    
    let videoElement: HTMLVideoElement;
    
    reader.decodeFromConstraints(
      {
        audio: false,
        video: {
          facingMode: 'environment',
        },
      },
      videoElement,
      (result, error) => {
        if (result) console.log(result);
        if (error) console.log(error);
      }
    );
    


    반응 구현


    useRef 후크를 사용하여 비디오 요소를 참조에 보관하고 useEffect로 디코딩을 시작할 수 있습니다. 가장 기본적인 구현은 다음과 같습니다.

    const BarcodeScanner = () => {
      const videoRef = useRef<HTMLVideoElement>(null);
      const reader = useRef(new BrowserMultiFormatReader());
    
      useEffect(() => {
        if (!videoRef.current) return;
        reader.current.decodeFromConstraints(
          {
            audio: false,
            video: {
              facingMode: 'environment',
            },
          },
          videoRef.current,
          (result, error) => {
            if (result) console.log(result);
            if (error) console.log(error);
          }
        );
        return () => {
          reader.current.reset();
        }
      }, [videoRef]);
    
      return <video ref={videoRef} />;
    };
    


    성능상의 이유로 BrowserMultiFormatReader 후크를 사용하여 useRef를 한 번만 인스턴스화하고 해당 인스턴스의 useEffect 메서드를 호출하여 reset()를 정리하는 것이 중요합니다.

    사용자 지정 후크 사용



    기본 구현을 살펴보면 다음과 같은 몇 가지 개선 영역이 있음을 알 수 있습니다.
  • 논리가 비디오 요소의 렌더링과 결합됩니다
  • .
  • 결과 또는 오류를 처리하지 않습니다
  • .
  • BarcodeScanner 소비자
  • 의 구성을 허용하지 않습니다.

    애플리케이션에서 비디오 요소를 렌더링하는 방법에서 논리를 분리할 수 있도록 사용자 지정 후크로 추출하여 개선할 수 있습니다.

    이것이 최종 구현입니다.

    import { BrowserMultiFormatReader, DecodeHintType, Result } from '@zxing/library';
    import { useEffect, useMemo, useRef } from 'react';
    
    interface ZxingOptions {
      hints?: Map<DecodeHintType, any>;
      constraints?: MediaStreamConstraints;
      timeBetweenDecodingAttempts?: number;
      onResult?: (result: Result) => void;
      onError?: (error: Error) => void;
    }
    
    const useZxing = ({
      constraints = {
        audio: false,
        video: {
          facingMode: 'environment',
        },
      },
      hints,
      timeBetweenDecodingAttempts = 300,
      onResult = () => {},
      onError = () => {},
    }: ZxingOptions = {}) => {
      const ref = useRef<HTMLVideoElement>(null);
    
      const reader = useMemo<BrowserMultiFormatReader>(() => {
        const instance = new BrowserMultiFormatReader(hints);
        instance.timeBetweenDecodingAttempts = timeBetweenDecodingAttempts;
        return instance;
      }, [hints, timeBetweenDecodingAttempts]);
    
      useEffect(() => {
        if (!ref.current) return;
        reader.decodeFromConstraints(constraints, ref.current, (result, error) => {
          if (result) onResult(result);
          if (error) onError(error);
        });
        return () => {
          reader.reset();
        };
      }, [ref, reader]);
    
      return { ref };
    };
    


    그런 다음 다음과 같은 구성 요소에서 사용할 수 있습니다.

    export const BarcodeScanner: React.FC<BarcodeScannerProps> = ({
      onResult = () => {},
      onError = () => {},
    }) => {
      const { ref } = useZxing({ onResult, onError });
      return <video ref={ref} />;
    };
    


    어떻게 생각하니?



    댓글로 알려주세요!

    좋은 웹페이지 즐겨찾기