🦁
React+TypeScriptなWebアプリで、QRコード読み込みしてみる。(jsQRで)
Abstract
React+TypescriptでQRコードを読み込む話をなるだけ簡素に。
"出来たよ!!"って記事は出てくるのに、React+Typescriptでってなると全然出てこなくって。
結論
今回の成果物はココ↓結論だけでよければ。
前提
- React+Typescriptの開発環境は構築済 Ubuntu22.04+VSCode+React+TypeScriptの開発環境を構築してみた。
- 空っぽプロジェクトにしててね。React+TypeScriptのプロジェクト雛形をgithubに置いとく話。
- ホストPC(Windows)のカメラ設定も忘れずに。
- [環境構築]VirtualBox上のLinux22.04でカメラを使う。
手順
1.プロジェクト生成 -> VSCodeで開く
めんどいから、プロジェクト雛形をgithubから持ってくる。React+TypeScriptのプロジェクト雛形をgithubに置いとく話。
2.生成したプロジェクトの場所でjsqrをインストール
$ cd プロジェクトのフォルダ
$ npm install jsqr --save
2.QRCodeScanner.tsxを作成
VSCodeで、src配下に、componentsフォルダを生成 -> QRCodeScanner.tsxを生成。
中身は以下で。
QRCodeScanner.tsx
import React from 'react';
const QRCodeScanner = () => {
return (
<div className="App">
hello QRCodeScanner
</div>
);
}
export default QRCodeScanner;
呼び元のApp.tsxは、2行追加
App.tsx
import React from 'react';
import './App.css';
+import QRCodeScanner from './components/QRCodeScanner';
function App() {
return (
<div className="App">
hello world!!
+ <QRCodeScanner />
</div>
);
}
export default App;
3.後は、QRCodeScanner.tsxを作り込んでいく。
後はもう、ココのコードを感謝しながらコピペする。ありがとうございます。
で、完了したんだけど、分かんないところがチラホラあって。
以下、まとめ。
3-1. MediaStreamConstraintsって何者?
Webカメラ操作する時に使用するCameraパラメータのクラス。何もしなくても、使える。(組み込み型?)
- 音声追加
- 撮像サイズ指定
- フレームレート指定
- フロントカメラ指定 とか
3-2. useRefって?
React hooksのひとつ。タグ要素への参照を保持する。以降、保持った参照で、タグ要素を操作できる。例:<input ret={aaa}>ってすると、aaa.currentに<input>の参照が設定されて、input要素をaaa.currentで操作できるようになると。
3-3. HTMLVideoElement って?
動画を web ページに取り込むなんか。詳細は→HTMLVideoElement
3-4. useEffectって?
React hooksのひとつ。コンポーネントのレンダー前後のイベントで、処理をさせることが出来る。詳細は→ useEffectをちゃんと理解する #React
4. QRCodeScanner.tsxの完成コード
QRCodeScanner.tsx
import React, { useEffect, useRef, useState } from 'react';
import jsQR from 'jsqr';
const videoWidth = 500;
const videoHeight = 500;
const videoFrameRate = 5;
const constraints: MediaStreamConstraints = {
audio: false,
video: {
width: { min: 1280, ideal: 1920, max: 2560 },
height: { min: 720 , ideal: 1080, max: 1440 } ,
frameRate: {
max: videoFrameRate,
}//,
// facingMode: {
// exact: 'environment',
// },
},
};
const QRCodeScanner = () => {
const videoRef = useRef<HTMLVideoElement>(null);
const intervalRef = useRef<number>();
const canvasRef = useRef<HTMLCanvasElement>(null);
const [isContinue, setIsContinue] = useState(false);
const [qrCodeData, setQrCodeData] = useState<string[]>([]);
useEffect(() => {
const openCamera = async () => {
const video = videoRef.current;
if (video) {
video.addEventListener('play', (event) => { console.log(event); console.log("(w,h)=(", video.videoWidth, ", " + video.videoHeight, ")") })
const stream = await navigator.mediaDevices.getUserMedia(constraints);
video.srcObject = stream;
}
};
openCamera();
}, [])
useEffect(() => {
if (!isContinue) {
return;
}
const decodeQRCode = () => {
const context = canvasRef?.current?.getContext('2d');
const video = videoRef?.current;
if (!context || !video) {
return;
}
context.beginPath();
context.fillStyle = 'rgb( 0, 0, 0)';
context.fillRect(0, 0, 100, 200);
context.drawImage(video, 0, 0, videoWidth, videoHeight);
const imageData = context.getImageData(0, 0, videoWidth, videoHeight);
const code = jsQR(imageData.data, videoWidth, videoHeight);
return code?.data;
};
const intervalId = window.setInterval(() => {
const decodedValue = decodeQRCode();
if (!decodedValue || qrCodeData.includes(decodedValue)) {
return;
}
setQrCodeData([...qrCodeData, decodedValue]);
}, 1_000 / videoFrameRate);
intervalRef.current = intervalId;
return () => {
clearInterval(intervalRef.current);
};
}, [isContinue, qrCodeData]);
const handleStart = () => {
setIsContinue(true);
};
const handleStop = () => {
setIsContinue(false);
};
return (
<div className="App">
<p>QR Code Scanner</p>
<div style={{ display: 'grid' }}>
<div>
<video autoPlay playsInline={true} ref={videoRef} style={{ width: '100%' }}>
<canvas width={videoWidth} height={videoHeight} ref={canvasRef} />
</video>
</div>
<div>
<p>{qrCodeData.join('\n')}</p>
</div>
<div>
<button onClick={handleStart}>Start Scan</button>
<button onClick={handleStop}>Stop Scan</button>
</div>
</div>
hello QRCodeScanner
</div>
);
}
export default QRCodeScanner;
で、実行。
出来た!!
けど、感度あんまりよくないかも。
Discussion