WebでQRコードリーダー

# 目次

[1.はじめに](#1)
[2.アプリの構造](#2)
[3.ソース解説](#3)
[4.あとがき](#4)

<h1 id="1">1.はじめに</h1>
…主に自分が見直すための記録です…

Web で QR コードリーダーを実装したので、その時のメモ。

#### ライブラリ

jsQR というライブラリを使いました。
[GitHub](https://github.com/cozmo/jsQR)から jsQR をクローンするか、[npm でインストール](https://www.npmjs.com/package/jsqr)します。

```sh:node
npm install jsqr --save
```

```javascript: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](https://chrome.google.com/webstore/detail/web-server-for-chrome/ofhbbkphhbklhfoeikjpcbhemlocgigb?hl=ja)

今回のアプリも Web Server for Chorme で動かす想定で作っています。

<h1 id="2">2.アプリの構造</h1>


| ファイル    | 簡単な解説                       |
| ----------- | -------------------------------- |
| app.css     | css                              |
| index.html  | メインで表示されるページ。       |
| index.js    | 画面関連を担当する JS ファイル   |
| jsQR.min.js | jsQR ライブラリを minfy したやつ |
| QRreader.js | QR リーダーを動かす JS ファイル  |

<h1 id="3">3.ソース解説</h1>
今回はボタンを押したらリーダーが始動するようにします。
アクセスと同時に起動させることもできますが、ポンコツPCだと常にビデオを起動させることに不安があったのでｗ
そのため、リーダーを停止させる stopボタンも用意しておきます。

起動ボタンは、一度押下すると再読み込みボタンになります。

```html: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>
```

```css: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 に切り替わるようにしました。

```javascript: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 は主にボタン押下時の動きを担当します。

```javascript: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();
});
```
![QR.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/721559/73259b3d-5057-09bf-9a3b-40682a049a3d.png)

<h1 id="4">4.あとがき</h1>
最近はWebで動くビデオ会議システムなんかも出てきて、ああいうのはネイティブアプリの領分だと思っていたのに…と戦慄してます。
JSのプログラマーの需要が増えるのはうれしい反面、学ぶべきことがどんどん増えていきますね…。

せっかくだから、classとかmoduleとか使って書けばよかったかな　なんて思ったり。