Loading...

2024-01-09(火) 15:00

🔍 Next.jsでQRコードリーダーを実装する

Next.jsQRCode
Next.jsでQRコードをカメラ経由で読み取ってその内容を表示するための実装方法を解説します。

目次

前提と注意事項

以下が前提と注意事項になります。

  • Next.js のバージョンは 14.0.5
  • Tailwind を使用しています。
  • console.logを多く残していたりエラーハンドリングはしていません。適宜実装してください。
  • html5-qrcodeというパッケージを使用します。 html-qrcodeはスター数も多く最近まで開発が進められていましたが、現在メンテナンスのみの状態になっており、新しいオーナーを探している状態です。以下のhtml-qrcodeの公式リポジトリを参照してください。
    Html5-QRCode

    Use this lightweight library to easily / quickly integrate QR code, bar code, and other common code scanning capabilities to your web application.

    github.com

この記事の内容は、以下の公式リポジトリにあるサンプルコードを参考にしています。

html5-qrcode with React

Example of using mebjas/html5-qrcode in React project with example, source and demo.

github.com

この記事のゴール

以下の画像のように、Next.js で QR コードをカメラ経由で読み取るための UI を実装して、読み取った内容を表示することをゴールとします。

実装するQRコードリーダーのUI

html5-qrcode をインストールする

npmかyarnを使って html5-qrcode のパッケージをインストールします。

my-project
$ npm install html5-qrcode
# or
$ yarn add html5-qrcode

また、QR コードリーダーで使用するカメラの一覧を表示、選択できるようにするために本記事ではreact-selectを使ったドロップダウンを使用します。そのためにreact-selectも追加しておきます。

my-project
$ yarn add react-select

なお、react-selectでなくても他のパッケージや自作の UI でも構いません。

QR コードリーダーのコンポーネントを作成する

以下のように、適当な場所にhtml5-qrcodeを使用した QR コードリーダーのためのコンポーネントを作成します。

app/components/Qrcode/QrcodeReader.tsx
'use client';
import { Html5Qrcode } from 'html5-qrcode';
import { useEffect, useState } from 'react';
import Select from 'react-select';
 
// QRコードリーダーの表示領域のhtmlのID
const qrcodeRegionId = 'html5qr-code-full-region';
 
export default function QrcodeReader({
  onScanSuccess,
  onScanFailure,
}: {
  onScanSuccess: any;
  onScanFailure: any;
}) {
  // QRコードリーダーの設定
  // fpsは読み取り頻度。デフォルトは 2.1秒間に何回読み取るかの値を設定。1ならば1秒間に1回読み取る。
  // qrboxは読み取り範囲の設定。widthとheightを設定する。
  const config = { fps: 1, qrbox: { width: 250, height: 250 } };
 
  // カメラの許可
  const [cameraPermission, setCameraPermission] = useState(false);
 
  // 選択したカメラID保存用
  const [selectedCameraId, setSelectedCameraId] = useState('');
 
  // 使用できるカメラ一覧
  const [cameras, setCameras] = useState<any>([]);
 
  // QRコードリーダーインスタンス
  const [html5QrcodeScanner, setHtml5QrcodeScanner] = useState<any>(null);
 
  // カメラ情報を取得するための関数
  const getCameras = async () => {
    await Html5Qrcode.getCameras()
      .then((cameras) => {
        if (cameras && cameras.length) {
          const formattedCameras = cameras.map((camera) => ({
            value: camera.id,
            label: camera.label || `Camera ${camera.id}`,
          }));
          setCameras(formattedCameras);
          setSelectedCameraId(formattedCameras[0].value);
          setCameraPermission(true);
        }
      })
      .catch((err) => {
        console.error(err);
      });
  };
 
  // スキャン開始
  const startScan = async () => {
    try {
      await html5QrcodeScanner.start(
        selectedCameraId,
        config,
        onScanSuccess,
        onScanFailure,
      );
      setHtml5QrcodeScanner(html5QrcodeScanner);
    } catch (error) {
      console.error('Error starting the scanner: ', error);
    }
  };
 
  // スキャン停止
  const stopScan = async () => {
    console.log('stop scan');
    try {
      await html5QrcodeScanner.stop();
      setHtml5QrcodeScanner(html5QrcodeScanner);
    } catch (error) {
      console.error('Error stopping the scanner: ', error);
    }
  };
 
  // カメラ切り替え
  const switchCamera = (targetId: string) => {
    console.log(targetId);
    setSelectedCameraId(targetId);
  };
 
  useEffect(() => {
    if (!onScanSuccess && !onScanFailure) {
      throw 'required callback.';
    }
 
    const scanner = new Html5Qrcode(qrcodeRegionId);
    setHtml5QrcodeScanner(scanner);
 
    return () => {
      scanner.clear();
    };
  }, []);
 
  return (
    <div className='container mx-auto'>
      <div className='max-w-screen-lg' id={qrcodeRegionId} />
      <div>
        {cameras.length > 0 ? (
          <Select
            name='camera'
            options={cameras}
            value={cameras.find(
              (camera: any) => camera.value === selectedCameraId,
            )}
            placeholder='カメラを選択'
            onChange={async (camera) => await switchCamera(camera.value)}
          />
        ) : (
          <p>カメラがありません</p>
        )}
      </div>
      <div>
        <button
          className='bg-blue-500 hover:bg-blue-700 text-white text-sm font-bold py-1 px-2 rounded mr-2'
          onClick={() => getCameras()}
        >
          カメラ取得
        </button>
        <button
          className='bg-blue-500 hover:bg-blue-700 text-white text-sm font-bold py-1 px-2 rounded mr-2'
          onClick={async () => await startScan()}
          disabled={!cameraPermission && selectedCameraId == ''}
        >
          スキャン開始
        </button>
        <button
          className='bg-red-500 hover:bg-red-700 text-white text-sm font-bold py-1 px-2 rounded'
          onClick={async () => await stopScan()}
        >
          スキャン停止
        </button>
      </div>
    </div>
  );
}

上記で設定しているhtml5-qrcodeのオプション使っているメソッドは全て以下の公式ドキュメントに記載されています。

Html5Qrcode

Low level APIs for building web based QR and Barcode Scanner.

scanapp.org

QR コードリーダーのコンポーネントを使用する

前節で作成したコンポーネントを使用して、読み取った QR コードの内容を表示するコンポーネントを作成します。

app/components/Qrcode/QrcodeReaderComponent.tsx
'use client';
import { useEffect, useState } from 'react';
import QrcodeReader from './QrcodeReader';
 
export default function QrcodeReaderComponent() {
  const [scannedTime, setScannedTime] = useState(new Date());
  const [scannedResult, setScannedResult] = useState('');
 
  useEffect(() => {}, [scannedTime, scannedResult]);
 
  // QRコードを読み取った時の実行する関数
  const onNewScanResult = (result: any) => {
    console.log('QRコードスキャン結果');
    console.log(result);
    setScannedTime(new Date());
    setScannedResult(result);
  };
  return (
    <>
      <div>
        <h2>スキャン日時:{scannedTime.toLocaleDateString()}</h2>
        <h2>スキャン結果:{scannedResult}</h2>
      </div>
      <QrcodeReader
        onScanSuccess={onNewScanResult}
        onScanFailure={(error: any) => {
          // console.log('Qr scan error');
        }}
      />
    </>
  );
}

上記で重要なのは、onNewScanResultという関数です。これは、QR コードを読み取った時に実行される関数で、読み取った値を引数に受け取り、その値をscannedResultにセットしています。 また、QrcodeReaderコンポーネントのonScanFailureには、読み取りに失敗した時に実行される関数を設定しています。本記事では、特に処理を記載していませんが、この関数は QR コードが検出されない場合にも実行されます。

動作確認

あとは以下のように適当なページにて、前節で作成したQrcodeReaderComponentを使用して動作確認を行います。

app/page.tsx
// import先は適宜置き換えてください。
import QrcodeReaderComponent from './components/Qrcode/QrcodeReaderComponent';
 
export default function Home() {
  return (
    <main>
      <div>
        <QrcodeReaderComponent />
      </div>
    </main>
  );
}

上記のページにアクセスすると、以下のように表示されます。

QRコードリーダーのデフォルト表示

上記でカメラ取得をクリックすると、ブラウザのカメラ使用許可ダイアログが表示されます。許可すると、使用できるカメラ一覧が表示され選択できます。 カメラを選択した状態でスキャン開始をクリックしてカメラを起動し、QR コードをカメラにかざすと、読み取った内容が同じページのスキャン結果の右側に表示されるはずです。

まとめ

本記事では、読み取った QR コードの内容を表示するのみですが、読み取った値を検証してその結果に応じて API へリクエストを送信したり、その他の処理を行うことができます。 例えば勤怠管理のための QR コードを読み取って、その内容を検証して勤怠管理システムへ HTTP リクエストを送信して処理するなども可能です。

もし Next.js で QR コードの生成も行いたい場合は、以下に別途まとめていますので必要な方は見てみてください。

🏁 Next.jsでQRコードを生成、ダウンロードできるようにする

Next.jsでQRコードを生成し、それをダウンロードできるようにするための実装方法を解説します。

ritaiz.com