[HTML5] QRコードリーダーを作成する

今回はHTML5の機能を利用した「QRコードリーダー」を作成します。 以前作ったJavaScriptによるカメラを操作するコードに、QRコードを読み取ってくれる便利ライブラリをくっつけただけのお手軽版。

PCやスマホに付いているカメラの映像を0.3秒毎にチェックし、QRコードが見つかったらQRに埋め込まれている情報を表示してくれます。見つかったQRコードの場所も簡単にマーキングする機能も付けてみました。

HTML5でQRコードリーダー

サンプル

以下のページから実際にサンプルを実行できます。 miku3.net

初回のアクセス時にWebブラウザから、このサイトにカメラの操作を許可して良いか聞かれますので「許可」ボタンをクリックしてください。「ブロック」ボタンを押すと設定を変更するのにメニューの少し深いところに潜る必要がありますのでご注意を。

準備

今回はこちらのjsQRライブラリを使います。 github.com

適当なディレクトリを作成し、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について詳しくは過去の記事を参照ください。 blog.katsubemakito.net

ソースコード

<!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("&", "&amp;");
  result = str.replace("'", "&#x27;");
  result = str.replace("`", "&#x60;");
  result = str.replace('"', "&quot;");
  result = str.replace("<", "&lt;");
  result = str.replace(">", "&gt;");
  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を元に画像に線を引いたというわけです。

参考ページ