今回はHTML5の機能を利用した「QRコードリーダー」を作成します。
以前作ったJavaScriptによるカメラを操作するコードに、QRコードを読み取ってくれる便利ライブラリをくっつけただけのお手軽版。
PCやスマホに付いているカメラの映像を0.3秒毎にチェックし、QRコードが見つかったらQRに埋め込まれている情報を表示してくれます。見つかったQRコードの場所も簡単にマーキングする機能も付けてみました。
HTML5でQRコードリーダー
サンプル
以下のページから実際にサンプルを実行できます。
初回のアクセス時にWebブラウザから、このサイトにカメラの操作を許可して良いか聞かれますので「許可」ボタンをクリックしてください。「ブロック」ボタンを押すと設定を変更するのにメニューの少し深いところに潜る必要がありますのでご注意を。
準備
今回はこちらのjsQR
ライブラリを使います。
適当なディレクトリを作成し、webpackから出力されたファイルがdistディレクトリの下にいるので直接取ってきます。
$ mkdir qrreader && cd qrreader
$ wget 'https://raw.githubusercontent.com/cozmo/jsQR/master/dist/jsQR.js'
このままだと若干ファイルサイズが大きいのでminifyします。大体半分くらいになりましたね。
$ uglifyjs --compress --mangle -- jsQR.js > jsQR.min.js
$ ls -lah
-rw-r--r-- 1 katsube staff 248K 6 22 15:40 jsQR.js
-rw-r--r-- 1 katsube staff 126K 6 22 15:40 jsQR.min.js
minifyについて詳しくは過去の記事を参照ください。
ソースコード
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>[HTML5] QRCode Reader</title>
<style>
/* 全体のレイアウト調整 */
#contents { display:flex; width:650px;}
#camera, #picture, #result { justify-content:center; margin:5px;}
/* リーダー部分 */
#picture { display:none; }
#result { border: 1px solid gray; width:300px; height:200px; padding:10px;}
small { color:gray; }
</style>
</head>
<body>
<h1>QRCode Reader</h1>
<section id="contents">
<!-- カメラ映像 -->
<video id="camera" width="300" height="200" muted></video>
<!-- 処理用 -->
<canvas id="picture" width="300" height="200"></canvas>
<!-- 読み取り結果 -->
<div id="result">
<small>※ここに読み取り結果が表示されます※</small>
</div>
</section>
<script src="jsQR.min.js"></script>
<script>
const video = $("#camera"); // === document.querySelector("#camera");
const canvas = $("#picture"); // === document.querySelector("#picture");
const ctx = canvas.getContext("2d");
window.onload = () => {
/** カメラ設定 */
const constraints = {
audio: false,
video: {
width: 300,
height: 200,
facingMode: "user" // フロントカメラを利用する
}
};
/**
* カメラを<video>と同期
*/
navigator.mediaDevices.getUserMedia(constraints)
.then( (stream) => {
video.srcObject = stream;
video.onloadedmetadata = (e) => {
video.play();
// QRコードのチェック開始
checkPicture();
};
})
.catch( (err) => {
console.log(err.name + ": " + err.message);
});
};
/**
* QRコードの読み取り
*/
function checkPicture(){
// カメラの映像をCanvasに複写
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
// QRコードの読み取り
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const code = jsQR(imageData.data, canvas.width, canvas.height);
//----------------------
// 存在する場合
//----------------------
if( code ){
// 結果を表示
setQRResult("#result", code.data); // 文字列
drawLine(ctx, code.location); // 見つかった箇所に線を引く
// video と canvas を入れ替え
canvas.style.display = 'block';
video.style.display = 'none';
video.pause();
}
//----------------------
// 存在しない場合
//----------------------
else{
// 0.3秒後にもう一度チェックする
setTimeout( () => {
checkPicture();
}, 300);
}
}
/**
* 発見されたQRコードに線を引く
*
* @param {Object} ctx
* @param {Object} pos
* @param {Object} options
* @return {void}
*/
function drawLine(ctx, pos, options={color:"blue", size:5}){
// 線のスタイル設定
ctx.strokeStyle = options.color;
ctx.lineWidth = options.size;
// 線を描く
ctx.beginPath();
ctx.moveTo(pos.topLeftCorner.x, pos.topLeftCorner.y); // 左上からスタート
ctx.lineTo(pos.topRightCorner.x, pos.topRightCorner.y); // 右上
ctx.lineTo(pos.bottomRightCorner.x, pos.bottomRightCorner.y); // 右下
ctx.lineTo(pos.bottomLeftCorner.x, pos.bottomLeftCorner.y); // 左下
ctx.lineTo(pos.topLeftCorner.x, pos.topLeftCorner.y); // 左上に戻る
ctx.stroke();
}
/**
* QRコードの読み取り結果を表示する
*
* @param {String} id
* @param {String} data
* @return {void}
*/
function setQRResult(id, data){
$(id).innerHTML = escapeHTML(data);
}
/**
* jQuery style wrapper
*
* @param {string} selector
* @return {Object}
*/
function $(selector){
return( document.querySelector(selector) );
}
/**
* HTML表示用に文字列をエスケープする
*
* @param {string} str
* @return {string}
*/
function escapeHTML(str){
let result = "";
result = str.replace("&", "&");
result = str.replace("'", "'");
result = str.replace("`", "`");
result = str.replace('"', """);
result = str.replace("<", "<");
result = str.replace(">", ">");
result = str.replace(/\n/, "<br>");
return(result);
}
</script>
</body>
</html>
解説
大雑把なロジック
video
タグにカメラをつなぎ、0.3秒置きに静止画をcanvas
タグにコピーしたあとに画像データとして取り出します。この画像データをjsQRライブラリに渡しチェック。もし画像内にQRコードが存在している場合はその内容を表示するサンプルになります。QRコード周りの面倒な処理はすべてライブラリが面倒を見てくれますので、それ以外の部分を書いた感じですね。
jsQRの使い方
jsQRは非常にシンプルで、canvasの内容を画像として取り出したあとに、jsQR()に渡し戻り値をチェックするだけです。存在しなければfalseになります。
<script src="jsQR.js"></script>
<script>
// Canvasの内容を画像として取り出す
const canvas = document.querySelector("canvas");
const ctx = canvas.getContetext("2d");
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// jsQRでチェック
const code = jsQR(imageData.data, canvas.width, canvas.height);
if (code) {
console.log("Found QR code", code);
}
</script>
戻り値はハッシュ(連想配列)として返ってきます。内容は以下の通り。
key | 説明 |
---|---|
binaryData | 認識したQRコードの画像データ(バイナリ) |
data | 認識したQRコード内にあるテキスト情報 |
location | 認識したQRコードが画像内のどこで見つかったかの座標情報 |
今回はcode.data
の内容を表示し、code.location
を元に画像に線を引いたというわけです。
参考ページ
このブログを応援する
お寄せいただいたお気持ちは全額サーバ代や次の記事を執筆するための原資として活用させていただいております。この記事が参考になった場合などぜひご検討ください。
スマホの自撮り用の方のカメラが起動する……
このサンプルはフロントカメラが起動するよう指定してますので、スマホで自撮り側のカメラが起動するのは正常な動作です。もしリアカメラが利用したい場合は以下の記事を参考になさってください。
https://blog.katsubemakito.net/html5/camera-toggle
読み込んだQRのサイトに飛びたい時は
「飛びたい時は」のあとは何でしょう?
もしご質問でしたらもう少し詳細をご記入いただけますか?
素晴らしいソースコードをありがとうございます。大変参考になりました。QRコードに枠が出るのもかっこいいです。
背面のカメラで動作させるために、もう一つのページにある情報を元にコードを見直ししていましたが、おそらく簡単なことだと思うのですがうまく動作していません。改修の方法を具体的に教えていただけないでしょうか。
ブログをご覧いただきありがとうございます。
どのカメラを利用するかはfacingModeで指定しますので、リアカメラを使いたい場合は変数constraintsを以下のように代入してやるだけです。
大変参考にさせて頂きました。
QRコードを2,3個認証したいのですがどうすればいいか
わかりましたら教えていただきたいです
ブログをご覧いただきありがとうございます。
ご質問の内容をもう少し詳しく教えていただけますか?
複数のQRコードを連続で読み取りたいといった意味でしょうか?
1つのカメラでQRコードを3個一気に認証したいです。
3つのQRコードを同時に認識したいということですね。
このページで利用しているjsQRをそのまま利用する場合、例えばですが以下のようなロジックで実装できるのではないかと思います。
(1) カメラの情報をCanvasに描画
(2) Canvas上にQRコードが見つかったらデータを取り出す
(3) QRコード部分を塗りつぶす
(4) 塗りつぶした画像を再度jsQRにかける
(5) 2〜3をQRコードが見つからなくなるまで繰り返す
理屈の上では行けると思いますが、実用性の面で課題が出てくるかもしれませんね。QRコードの一部が微妙に隠れたり斜めになった状態でカメラに写り込んだ場合は同時に認識してくれないケースが考えられます。恐らく現実的には「同時」ではなく、「連続」での仕様を検討されても良いかもしれませんね。
あと少し気になったのですが、「QRコードリーダー」はQRコード内のデータを読み取るだけで、何らかの「認証」をしているわけではありません。パソコンでファイルを開いて中身を覗いているようなイメージです。例えば本人かどうかを確認するような「認証」にご使用される場合は、別途そのロジックを実装する必要があります。