目次
1.はじめに
2.アプリの構造
3.ソース解説
4.あとがき
1.はじめに
…主に自分が見直すための記録です…Web で QR コードリーダーを実装したので、その時のメモ。
ライブラリ
jsQR というライブラリを使いました。
GitHubから jsQR をクローンするか、npm でインストールします。
node
npm install jsqr --save
QRreader.js
import jsQR from "jsqr";
// CommonJS require
const jsQR = require("jsqr");
jsQR(...);
QR を読み込むためにデバイスのカメラにアクセスします。そのため、https もしくは localhost 環境下でのみ稼働します。
node.js とか使って localhost サーバを立ててもいいのですが、たぶん一番簡単なのは Chrome の拡張機能の Web Server for Chorme です。
Web Server for Chorme
今回のアプリも Web Server for Chorme で動かす想定で作っています。
2.アプリの構造
ファイル | 簡単な解説 |
---|---|
app.css | css |
index.html | メインで表示されるページ。 |
index.js | 画面関連を担当する JS ファイル |
jsQR.min.js | jsQR ライブラリを minfy したやつ |
QRreader.js | QR リーダーを動かす JS ファイル |
3.ソース解説
今回はボタンを押したらリーダーが始動するようにします。 アクセスと同時に起動させることもできますが、ポンコツPCだと常にビデオを起動させることに不安があったのでw そのため、リーダーを停止させる stopボタンも用意しておきます。起動ボタンは、一度押下すると再読み込みボタンになります。
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>QR Reader test</title>
<link rel="stylesheet" href="app.css">
<script src="jsQR.min.js"></script>
</head>
<body>
<h1>QR Reader test</h1>
<p>URL情報を取得して表示します</p>
<button id="btn" class="start">
<span id="startBtn">start QRreader</span>
<span id="restartBtn">再読み込み</span>
</button>
<button id="stopBtn">stop</button>
<div id='loadingMessage'> starボタンを押すとReaderが起動します</div>
<video id='video' hidden></video>
<canvas id="canvas" hidden></canvas>
<div id="output" hidden>
<div id="outputMessage">No QR code detected.</div>
<div hidden>
<b>URL:</b>
<span id="outputData"></span>
</div>
</div>
<script src="QRreader.js"></script>
<script src="index.js"></script>
</body>
</html>
app.css
body {
font-family: "Ropa Sans", sans-serif;
color: #333;
max-width: 640px;
margin: 0 auto;
position: relative;
}
h1 {
margin: 10px 0;
font-size: 40px;
}
#loadingMessage {
text-align: center;
padding: 40px;
background-color: #eee;
}
#canvas,
#video {
width: 100%;
}
#output {
margin-top: 20px;
background: #eee;
padding: 10px;
padding-bottom: 0;
}
#output div {
padding-bottom: 10px;
word-wrap: break-word;
}
#startBtn {
display: block;
}
#restartBtn {
display: none;
}
続いて QRreader.js です。
QR 読み取りに成功すると video から Canvas に切り替わるようにしました。
QRreader.js
const video = document.createElement('video');
const canvasElement = document.getElementById('canvas');
const canvas = canvasElement.getContext('2d');
const loadingMessage = document.getElementById('loadingMessage');
const outputContainer = document.getElementById('output');
const outputMessage = document.getElementById('outputMessage');
const outputData = document.getElementById('outputData');
const startQR = () => {
navigator.mediaDevices.getUserMedia({
video: {
audio: false,
facingMode: 'environment'//'user' でインカメを使う
}
}).then((stream) => {
video.srcObject = stream;
video.setAttribute('playsinline', true);
video.play();
requestAnimationFrame(tick);
}).catch((err) => {
alert(err.message)
})
};
//QRの解析
function tick() {
loadingMessage.innerHTML = 'Loading video...';
if (video.readyState === video.HAVE_ENOUGH_DATA) {
loadingMessage.hidden = true;
canvasElement.hidden = false;
outputContainer.hidden = false;
canvasElement.height = video.videoHeight;
canvasElement.width = video.videoWidth;
canvas.drawImage(video, 0, 0, canvasElement.width, canvasElement.height);
//CanvasのgetImageDataメソッドで指定範囲のImageDataオブジェクトを取得する
const imageData = canvas.getImageData(0, 0, canvasElement.width, canvasElement.height);
//jsQRのメソッド
const code = jsQR(imageData.data, imageData.width, imageData.height, {
inversionAttempts: 'dontInvert',
});
//QRが読み込めた時の挙動
if (code) {
drawLine(code.location.topLeftCorner, code.location.topRightCorner, "#FF3B58");
drawLine(code.location.topRightCorner, code.location.bottomRightCorner, "#FF3B58");
drawLine(code.location.bottomRightCorner, code.location.bottomLeftCorner, "#FF3B58");
drawLine(code.location.bottomLeftCorner, code.location.topLeftCorner, "#FF3B58");
outputMessage.hidden = true;
outputData.parentElement.hidden = false;
//読み取ったURLをリンクにして表示
outputData.innerHTML = `<a href=${code.data}>${code.data}</a>`;
//videoをcanvasに
video.style.display = 'none';
video.pause();
} else {
outputMessage.hidden = false;
outputData.parentElement.hidden = true;
}
}
requestAnimationFrame(tick);
};
//QRを囲むライン
const drawLine = (begin, end, color) => {
canvas.beginPath();
canvas.moveTo(begin.x, begin.y);
canvas.lineTo(end.x, end.y);
canvas.lineWidth = 4;
canvas.strokeStyle = color;
canvas.stroke();
};
//off
const videoOff = () => {
video.pause();
video.src = "";
video.srcObject.getTracks()[0].stop();
};
最後に、 index.js を記述します。
index.js は主にボタン押下時の動きを担当します。
index.js
const btn = document.getElementById('btn');
const stopBtn = document.getElementById('stopBtn');
const changeBtn = () => {
const btnStart = document.getElementById('startBtn');
const btnRestart = document.getElementById('restartBtn');
const toggleBtn = document.getElementById('btn');
const toggleBtnClass = toggleBtn.getAttribute('class');
//startボタンと再読み込みボタンを切り替える
if (toggleBtnClass === 'start') {
toggleBtn.classList.remove('start');
toggleBtn.classList.add('restart');
btnStart.style.display = 'none';
btnRestart.style.display = 'block';
startQR();
} else if (toggleBtnClass === 'restart') {
startQR();
outputData.innerText = 'No QR code detected.';
}
};
btn.addEventListener('click', () => {
changeBtn();
});
stopBtn.addEventListener('click', () => {
videoOff();
});
4.あとがき
最近はWebで動くビデオ会議システムなんかも出てきて、ああいうのはネイティブアプリの領分だと思っていたのに…と戦慄してます。 JSのプログラマーの需要が増えるのはうれしい反面、学ぶべきことがどんどん増えていきますね…。せっかくだから、classとかmoduleとか使って書けばよかったかな なんて思ったり。