LoginSignup
2
1

More than 3 years have passed since last update.

WebでQRコードリーダー

Last updated at Posted at 2021-02-28

目次

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();
});

QR.png

4.あとがき

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

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

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1