前提と注意事項
以下が前提と注意事項になります。
- 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 を実装して、読み取った内容を表示することをゴールとします。
html5-qrcode をインストールする
npm
かyarn
を使って html5-qrcode
のパッケージをインストールします。
$ npm install html5-qrcode
# or
$ yarn add html5-qrcode
また、QR コードリーダーで使用するカメラの一覧を表示、選択できるようにするために本記事ではreact-select
を使ったドロップダウンを使用します。そのためにreact-select
も追加しておきます。
$ yarn add react-select
なお、react-select
でなくても他のパッケージや自作の UI でも構いません。
QR コードリーダーのコンポーネントを作成する
以下のように、適当な場所にhtml5-qrcode
を使用した QR コードリーダーのためのコンポーネントを作成します。
'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
のオプション使っているメソッドは全て以下の公式ドキュメントに記載されています。
QR コードリーダーのコンポーネントを使用する
前節で作成したコンポーネントを使用して、読み取った QR コードの内容を表示するコンポーネントを作成します。
'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
を使用して動作確認を行います。
// import先は適宜置き換えてください。
import QrcodeReaderComponent from './components/Qrcode/QrcodeReaderComponent';
export default function Home() {
return (
<main>
<div>
<QrcodeReaderComponent />
</div>
</main>
);
}
上記のページにアクセスすると、以下のように表示されます。
上記でカメラ取得
をクリックすると、ブラウザのカメラ使用許可ダイアログが表示されます。許可すると、使用できるカメラ一覧が表示され選択できます。
カメラを選択した状態でスキャン開始
をクリックしてカメラを起動し、QR コードをカメラにかざすと、読み取った内容が同じページのスキャン結果
の右側に表示されるはずです。
まとめ
本記事では、読み取った QR コードの内容を表示するのみですが、読み取った値を検証してその結果に応じて API へリクエストを送信したり、その他の処理を行うことができます。 例えば勤怠管理のための QR コードを読み取って、その内容を検証して勤怠管理システムへ HTTP リクエストを送信して処理するなども可能です。
もし Next.js で QR コードの生成も行いたい場合は、以下に別途まとめていますので必要な方は見てみてください。