Как сканировать штрих-коды в приложении React.js


Обложка: «кто-то сканирует штрих-код упаковки своим смартфоном, карикатура» — DALL-E mini

EDIT: Теперь я опубликовал это в новом пакете NPM, как react-zxing.

Справочная информация

Это моя первая статья. Меня побудило написать ее то, что я обнаружил, что это особенно сложно реализовать, я не смог найти никакого хорошего учебника, а мой пост на reddit, похоже, вызвал некоторый интерес.

Что мы создаем?

Я хотел использовать сканер штрих-кодов для своего сайд-проекта Snxbox. Моими критериями были:

  • Транслировать выход камеры устройства пользователя на видеоэлемент, чтобы пользователь мог видеть, на что он нацелил камеру.
  • Точное определение QR- и EAN-кодов из потока и выдача результатов.

Альтернативы

Я начал искать совместимые с React пакеты, которые можно было бы использовать. Первый пакет, который я нашел, был react-qr-barcode-scanner, который предлагал простой компонент реакта.

react-qr-barcode-scanner

react-qr-barcode-scanner полагается на zxing для декодирования штрих-кодов. Я использовал его некоторое время, пока не обнаружил ошибку, вызванную непоследовательными результатами при считывании EAN-кодов. Я нашел проблему на zxing и, похоже, она была исправлена. Однако react-qr-barcode-scanner использовал более старую версию zxing, где эта проблема все еще была.

quokka2.

Это еще один пакет, расширяющий zxing. Я нашел пример того, как использовать его с React, но, честно говоря, это показалось мне сложным.

html5-qrcode

Еще один пакет, расширяющий zxing. Реализовать его было немного проще, хотя, похоже, он также использует старую версию zxing, поэтому я был немного осторожен с его использованием.

Использование API для обнаружения штрихкодов

Существует экспериментальный API для сканирования штрих-кодов, но, к сожалению, его поддержка пока ограничена.

Попытка рефакторинга

В конце концов я форкнул react-qr-barcode-scanner в попытке обновить его зависимости, но обнаружил, что реализация была довольно простой.

Кроме того, 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);
  }
);
Вход в полноэкранный режим Выход из полноэкранного режима

Реализация React

Мы можем держать видеоэлемент в ссылке, используя хук 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} />;
};
Вход в полноэкранный режим Выход из полноэкранного режима

Что вы думаете?

Пожалуйста, дайте мне знать, оставив комментарий!

Оцените статью
devanswers.ru
Добавить комментарий