4
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

便利ページ:JavascriptでQRコードスキャン

Last updated at Posted at 2020-01-13

久しぶりの、「便利ページ:Javascriptでちょっとした便利な機能を作ってみた」 のシリーズものです。

今回は、QRコードスキャンです。
QRコード生成はすでに実装してあったのですが、スキャンする方はありませんでした。

HTML5では、PCに接続されたカメラを扱うことができますので、ブラウザだけでスキャンできます。また、AndroidやiPhoneでのChromeでも動作するので、これでPCだけでなくスマホでも動きます。

いつもの通りGitHubにも上げてあります。
 https://github.com/poruruba/utilities

参考までに、以下にデモとしてアクセスできるようにしてあります。
 https://poruruba.github.io/utilities/

(2020/2/2 修正)
・画像サイズを300px固定ではなく、カメラ画像のサイズに合わせるようにしました。

QRコードスキャンのためのライブラリ

以下を使わせていただきました。ありがとうございます。

cozmo/jsQR
 https://github.com/cozmo/jsQR

HTMLで以下のようにスクリプトをロードしておきます。
 <script src="dist/js/jsQR.js"></script>

ソースコード抜粋

肝心のJavascriptのソースコードです。重要部分のみ抜粋しています。

start.js
        qrcode_scan: function(){
            this.qrcode_video = $('#qrcode_camera')[0];
            this.qrcode_canvas = $('#qrcode_canvas')[0];

            if( this.qrcode_running ){
                this.qrcode_forcestop();
                return;
            }

            this.qrcode_running = true;
            this.qrcode_btn = 'QRスキャン停止';

            this.qrcode_timer = setTimeout(() =>{
                this.qrcode_forcestop();
            }, QRCODE_CANCEL_TIMER);

            return navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" }, audio: false })
            .then(stream =>{
                this.qrcode_scaned_data = "";
                this.qrcode_video.srcObject = stream;
                this.qrcode_draw();
            })
            .catch(error =>{
                alert(error);
            });
        },
        qrcode_draw: function(){
//            console.log(this.qrcode_video.videoWidth, this.qrcode_video.videoHeight);
            if( this.qrcode_context == null ){
                if( this.qrcode_video.videoWidth == 0 || this.qrcode_video.videoHeight == 0 ){
                    if( this.qrcode_running )
                        requestAnimationFrame(this.qrcode_draw);
                    return;
                }
                this.qrcode_canvas.width = this.qrcode_video.videoWidth;
                this.qrcode_canvas.height = this.qrcode_video.videoHeight;
                this.qrcode_context = this.qrcode_canvas.getContext('2d');
            }
            this.qrcode_context.drawImage(this.qrcode_video, 0, 0, this.qrcode_canvas.width, this.qrcode_canvas.height);
            const imageData = this.qrcode_context.getImageData(0, 0, this.qrcode_canvas.width, this.qrcode_canvas.height);

            const code = jsQR(imageData.data, this.qrcode_canvas.width, this.qrcode_canvas.height);
            if( code && code.data != "" ){
                this.qrcode_scaned_data = code.data;
                console.log(code);
                
                this.qrcode_forcestop();

                this.qrcode_context.strokeStyle = "blue";
                this.qrcode_context.lineWidth = 3;
                
                var pos = code.location;
                this.qrcode_context.beginPath();
                this.qrcode_context.moveTo(pos.topLeftCorner.x, pos.topLeftCorner.y);
                this.qrcode_context.lineTo(pos.topRightCorner.x, pos.topRightCorner.y);
                this.qrcode_context.lineTo(pos.bottomRightCorner.x, pos.bottomRightCorner.y);
                this.qrcode_context.lineTo(pos.bottomLeftCorner.x, pos.bottomLeftCorner.y);
                this.qrcode_context.lineTo(pos.topLeftCorner.x, pos.topLeftCorner.y);
                this.qrcode_context.stroke();
            }else{
                if( this.qrcode_running )
                    requestAnimationFrame(this.qrcode_draw);
            }
        },
        qrcode_forcestop: function(){
            if( !this.qrcode_running )
                return;

            this.qrcode_running = false;

            if( this.qrcode_timer != null ){
                clearTimeout(this.qrcode_timer);
                this.qrcode_timer = null;
            }

            this.qrcode_video.pause();
            this.qrcode_video.srcObject = null;
            this.qrcode_btn = 'QRスキャン開始';
        },

ご参考までに、HTMLの方も。

index.html
    <label>scaned data</label>
    <div class="input-group">
        <span class="input-group-btn">
            <button class="btn btn-default clip_btn glyphicon glyphicon-paperclip" data-clipboard-target="#qrcode_scaned_data"></button>
        </span>
        <input id="qrcode_scaned_data" type="text" class="form-control" v-model="qrcode_scaned_data" readonly>
    </div><br>
    <button class="btn btn-primary" v-on:click="qrcode_scan()">{{qrcode_btn}}</button><br>
    <div>
        <img v-show="!qrcode_running && qrcode_scaned_data==''" id="qrcode_start" class="img-responsive" src="./img/qrcode_start.png"><br>
        <video v-show="qrcode_running" id="qrcode_camera" class="img-responsive" autoplay></video>
        <canvas v-show="!qrcode_running && qrcode_scaned_data!=''" id="qrcode_canvas" class="img-responsive"></canvas>
    </div>

解説

・qrcode_scan()
ボタン押下をトリガに、QRコードスキャンを開始します。
まずは、「navigator.mediaDevices.getUserMedia」を呼び出して、ユーザに対してカメラ利用の許可を聞いた後にカメラを起動させます。
起動した後、「this.qrcode_video.srcObject = stream;」でカメラ画像をWebページに表示させ、「qrcode_draw()」の呼び出しで、カメラ画像からQRコードを探します。
setTimeout()がありますが、一定時間QRコードスキャンで見つからなかったときに、QRコードスキャンを停止するためのものです。
ちなみに、ブラウザからカメラを利用するため、HTMLはHTTPSでホスティングしている必要があります。

・qrcode_draw()
カメラ画像からQRコードをスキャンします。
いったん、別のcanvasのcontextにコピーしたのち、QRコードスキャンのライブラリを呼び出します。
QRコードがあった場合には、QRコードの部分を青色の四角形で囲ってスキャン終了です。
もしQRコードがなかった場合には、「requestAnimationFrame」を呼び出して次のカメラ画像を待ちます。

補足

CPU負荷を低減せねば。。。

以上

4
8
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
4
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?