Nuxt.jsで開発しているWebサービスにバーコードリーダ機能をつけようとしたら、
いろいろハマったので、そのときの備忘録。
利用したのはQuaggaJS。簡単に使えて便利(´ω`)
はまったポイントは、以下の4点...
- httpsじゃないとカメラを取得できない
- QuaggaJSで表示されてないHTML要素を指定するとエラー
- size/width/heightを指定してもいい感じにならない
- iOSのPWAではカメラにアクセスできない
いろいろハマったけど、QuaggaJS自体がすごく良いので、サクッとできた♪
作ったのはこんな感じ
バーコードリーダ、できてきた♪
— 積読ハウマッチ📚きらぷか (@kira_puka) September 17, 2019
いい感じな気がする(*´ω`*) pic.twitter.com/eDJH4P57pZ
動きとしては、こんな感じでシンプル。
- ボタンを押すと、モーダルが開いて、カメラ移動
- バーコードを読み取り終えると、終了してPageに結果を返す
QuaggaJSを使ってみる
使い方はこんな感じ。
まずはインストール
$ npm i -S quagga
バーコードリーダモーダルはこんな感じ
CSSにはBulmaを利用してます。
<template>
<div class="dialog modal is-active" v-if="active">
<div class="modal-background"></div>
<div class="modal-content">
<!-- カメラの映像を表示させるDIV -->
<div id="camera-area" class="camera-area"></div>
</div>
<!-- 右上の閉じるボタン -->
<button class="modal-close is-large" aria-label="close" @click.prevent.stop="onClickCancel"></button>
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop, Emit, Watch } from "nuxt-property-decorator";
@Component
export default class ModalReader extends Vue {
// モーダルの表示/非表示のprop
@Prop({ required: true }) active!: boolean;
// QuaggaJSのインスタンス
private Quagga;
/**
* Vueインスタンス破棄時、カメラを起動していたらストップする
*/
destroyed() {
if (!!this.Quagga) this.Quagga.stop();
}
// ****************************************************
// * watch
// ****************************************************
/**
* this.activeを監視してQuaggaの開始/停止をする
*/
@Watch("active")
private onChangeActive(val: boolean) {
this.$nextTick(() => {
if (val) {
// モーダル表示時、Quaggaを起動
this.initQuagga();
} else {
// モーダル非表示時、Quaggaを停止
if (!!this.Quagga) this.Quagga.stop();
}
});
}
// ****************************************************
// * methods
// ****************************************************
/**
* Quaggaの初期化処理
*/
private initQuagga() {
// requireで読み込み
this.Quagga = require("quagga");
// バーコード検出時の処理を設定
this.Quagga.onDetected(this.onDetected);
// Quaggaの設定項目
const config = {
// カメラの映像の設定
inputStream: {
type: "LiveStream",
// カメラ映像を表示するHTML要素の設定
target: document.querySelector("#camera-area"),
// バックカメラの利用を設定. (フロントカメラは"user")
constraints: { facingMode: "environment" },
// 検出範囲の指定: 上下30%は対象外
area: { top: "30%", right: "0%", left: "0%", bottom: "30%" }
},
// 解析するワーカ数の設定
numOfWorkers: navigator.hardwareConcurrency || 4,
// バーコードの種類を設定: ISBNは"ean_reader"
decoder: { readers: ["ean_reader"] }
}
// 初期化の開始。合わせて、初期化後の処理を設定
this.Quagga.init(config, this.onInitilize);
}
/**
* Quaggaの初期化完了後の処理
* errorがなければ、起動する
*/
private onInitilize(error) {
if (!!error) {
// エラーがある場合は、キャンセルをEmitする
console.error(`Error: ${error}`, error);
this.onClickCancel();
return;
}
// エラーがない場合は、読み取りを開始
console.info("Initialization finished. Ready to start");
this.Quagga.start();
}
/**
* バーコード検出時の処理
*/
private onDetected(success) {
// ISBNは'success.codeResult.code'から取得
const isbn = success.codeResult.code;
// ISBNをEmitで返却する
this.onSuccess(isbn);
}
// ****************************************************
// * emit
// ****************************************************
@Emit("cancel")
private onClickCancel() {}
@Emit("success")
private onSuccess(code) {
return code;
}
}
</script>
<style lang="scss">
.camera-area {
overflow: hidden;
height: 300px;
width: 300px;
/**
* 指定したDIV配下にvideoとcanvasが追加される
* 4:3になるため、margin-topで調整
*/
video, canvas {
margin-top: -50px;
width: 300px;
height: 400px;
}
}
</style>
ページ側でpropのactiveを切り替えて、モーダルの表示をすればOK。簡単(´ω`)
videoやcanvasなどは、Quaggaが生成するよう。
はまったポイント...
PC上で開発しているときは割とサクッとできたんですが、
テストでいろんなところにハマリポイントが..
1. httpsじゃないとカメラを取得できない
ローカルで実装できたので、nuxt.config.tsを以下のようにして、
const config: NuxtConfiguration = {
server: {
port: 3000,
host: "0.0.0.0"
}
};
Androidスマホから試してみたら、こんなエラーが...
Error: getUserMedia is not defined
以下の記事を見ると、Chromeではlocalhostかhttpsじゃないと動かないらしい...
参考: ChromeではgetUserMediaがHTTPS経由でないと動かなくなっていた – 打つか投げるか
以下のようなローカルで起動したNuxt.jsをhttps化する方法もあるけど、
ステージング環境のFirebaseプロジェクトを用意して対応。。
参考: Nuxt.jsでlocalhostをSSL化する方法 - Qiita
2. QuaggaJSで表示されてないHTML要素を指定するとエラー
初期化処理の以下の部分で#camera-area
を指定してるが、
最初はmounted内で行っていた。
private initQuagga() {
// Quaggaの設定項目
const config = {
// カメラの映像の設定
inputStream: {
// カメラ映像を表示するHTML要素の設定
target: document.querySelector("#camera-area"),
},
}
}
すると、こんなエラーが...
error TypeError: Cannot read property 'setAttribute' of undefined
at Object.o.createLiveStream
よく見直してみると、v-if="active"
で表示を切り替えているので、
document.querySelector("#camera-area")
で取得できてなかった...
3. size/width/heightを指定してもいい感じにならない
公式サイトのExampleに以下のサンプルがあったので試してみたところ、
あまりいい感じではなかった...
inputStream: {
size: 800 // restrict input-size to be 800px in width (long-side)
}
#camera-area
のwidthを300にしていたため、
size: 300
で指定してみたところ、なかなか検出されず。。。
この設定を外すとすぐに検出できた( ゚д゚)!
GitHubにあるexampleのquaggaJS/live_w_locator.htmlを見てみると、
CSSで画面サイズとかを設定しているようなので、そちらを参考にした。
以下のように、constraintsでwidthとheightを指定できそうだったが、
MediaDevices.getUserMedia()で利用するMediaStreamConstraintsの解像度の制約のよう...
inputStream: {
constraints: {
width: 640,
height: 480,
}
}
日本語の情報が少ないので、トライ&エラーで確認...
4. iOSのPWAではカメラにアクセスできない
Androidもうまくいき、iOSのブラウザでもできるようになったが、
iOSのPWAで確認したところ、またこのエラーが...
Error: getUserMedia is not defined
以下の記事を見てみると、iOSのPWAでは制限があるらしい...
iOSのPWAでは表示モードがstandaloneの場合にカメラを開くことができない。
参考: PWAでカメラを使うためiOSとAndroidで異なるmanifestを読み込む - Qiita
なんてこった。。ほんとうに残念である。。
仕方がないので参考記事にあるように、
Nuxt.jsでもmanifest.jsonを2つ用意してUAで切り替えるように変更。。
【暫定対処】Nuxt.jsでOSに応じてmanifest.jsonを切り替える
方法としてはこんな感じ。
- iOS用のmanifest.json(manifest_ios.json)を用意する
- UA応じたmanifest.jsonの
<link>
を生成するpluginを作成 - 作ったプラグインをnuxt.config.tsに設定
1. iOS用のmanifest.jsonの作成
参考記事にある通り、manifest.jsonをコピーして、
"display": "standalone"
を"display": "browser"
にしただけの
manifest_ios.jsonを作成する。
--- static/manifest.json 2019-09-17 21:29:18.000000000 +0900
+++ static/manifest_ios.json 2019-09-18 00:21:54.000000000 +0900
@@ -3,7 +3,7 @@
"short_name": "積読ハウマッチ",
"description": "買った本を読まずに積んでおく「積読」、全部でいくらか知っていますか?「積読ハウマッチ」は積んである本の総額がわかる書籍管理サービスです。",
"start_url": "/",
- "display": "standalone",
+ "display": "browser",
"background_color": "#ffffff",
"theme_color": "#776f59",
"orientation": "portrait-primary",
2. UA応じた<link>
を生成するpluginを作成
以下のplugins/pwa-setup.ts
を作成。
const userAgent = navigator.userAgent.toLowerCase();
const iOS = userAgent.indexOf("iphone") > 0 || userAgent.indexOf("ipad") > 0;
// manifestのlinkタグを生成
function setManifest(path) {
const manifest = document.createElement("link");
manifest.rel = "manifest";
manifest.href = path;
document.head.appendChild(manifest);
}
setManifest(iOS ? "/manifest_ios.json" : "/manifest.json");
3. nuxt.config.tsの設定
作ったプラグインをnuxt.config.tsに設定。
const config: NuxtConfiguration = {
plugins: [
{ src: "~/plugins/pwa-setup", ssr: false }
]
}
これでNuxt.jsでもUAに応じて切り替えができるように(´ω`)
ただ、iOSのPWAがただのブラウザショートカットに...
おまけ: その他、もろもろ
上記以外のQuaggaJSの使い方や開発で役立ったことを五月雨に。
A) Android実機のデバッグはDevToolsでできる
Android実機でChromeを開いて確認していた時、コンソールログがみたいなと思ったら、
DevToolsでリモートデバッグできた!! DevTollsすごい(´ω`)
- Android 端末のリモート デバッグを行う | Tools for Web Developers | Google Developers
- Android の Chrome で開発者ツールを使う方法 - Qiita
バーコードリーダっぽく四角い枠をつける
これ。
#camera-area
内にdivをもたせて表示させている
<template>
<div class="dialog modal is-active" v-if="active">
<div class="modal-background"></div>
<div class="modal-content">
<div id="camera-area" class="camera-area">
<!-- 青い四角のDIV -->
<div class="detect-area"></div>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click.prevent.stop="onClickCancel"></button>
</div>
</template>
<style lang="scss">
.camera-area {
margin: auto;
overflow: hidden;
height: 300px;
width: 300px;
/* relativeに設定 */
position: relative;
video, canvas {
margin-top: -50px;
width: 300px;
height: 400px;
}
/* 検出範囲のサイズに合わせ枠線を引く */
.detect-area {
position: absolute;
top: 30%;
bottom: 30%;
left: 10%;
right: 10%;
border: 2px solid #0000ff;
}
}
</style>
解析中っぽく緑の枠を出す
読み取っている箇所を表示できるよう解析中のコールバックがある。
こちらの記事を参考に、解析中の状況を表示する。
<script lang="ts">
@Component
export default class ModalBarcodeReader extends Vue {
private initQuagga() {
this.Quagga = require("quagga");
// 解析中に呼び出される処理を設定
this.Quagga.onProcessed(this.onProcessed);
this.Quagga.onDetected(this.onDetected);
// ...
}
/**
* バーコード読み取り中時の処理
*/
private onProcessed(data) {
const ctx = this.Quagga.canvas.ctx.overlay;
const canvas = this.Quagga.canvas.dom.overlay;
if (!data) return;
// 認識したバーコードを緑の枠で囲む
if (data.boxes) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const hasNotRead = box => box !== data.box;
data.boxes.filter(hasNotRead).forEach(box => {
this.Quagga.ImageDebug.drawPath(box, { x: 0, y: 1 }, ctx, { color: "green", lineWidth: 2 });
});
}
// 読み取ったバーコードを青の枠で囲む
if (data.box) {
this.Quagga.ImageDebug.drawPath(data.box, { x: 0, y: 1 }, ctx, { color: "blue", lineWidth: 2 });
}
// 読み取ったバーコードに赤い線を引く
if (data.codeResult && data.codeResult.code) {
this.Quagga.ImageDebug.drawPath(data.line, { x: "x", y: "y" }, ctx, { color: "red", lineWidth: 3 });
}
}
}
</script>
<style lang="scss">
.camera-area {
/* ... */
/* オーバーレイ */
.drawingBuffer {
position: absolute;
left: 0;
}
}
</style>
以下のサンプル画像のように、
読み取ったバーコードの枠を囲ったり、線を引いたりもできる(´ω`)
おわりに
Nuxt.jsアプリでもQuaggaJSで簡単にバーコードリーダが作れる(´ω`)
ただ、getUserMedia()にも、iOSのPWAには罠が。。
こんなのつくってます!!
バーコードリーダ機能もある積読用の読書管理アプリ
『積読ハウマッチ』をリリースしました!
積読ハウマッチは、Nuxt.js+Firebaseで開発してます!
もしよかったら、遊んでみてくださいヽ(=´▽`=)ノ
要望・感想・アドバイスなどあれば、
公式アカウント(@MemoryLoverz)や開発者(@kira_puka)まで
参考にしたサイト
- QuaggaJS, an advanced barcode-reader written in JavaScript
- Quagga Sandbox
- quaggaJS/live_w_locator.html at master · serratus/quaggaJS
- QuaggaJSを使ったバーコードリーダ実装 | WatchContents
- Quagga.jsを使ってブラウザ上からJavaScriptでバーコードを読み取る | Black Everyday Company
- PWAでカメラを使うためiOSとAndroidで異なるmanifestを読み込む - Qiita
- MediaDevices.getUserMedia() - Web API | MDN
- MediaDevices.enumerateDevices() - Web API | MDN
- ブラウザからメディアデバイスを操る - getUserMedia()の基本 | CodeGrid
- ブラウザでバーコード/QRコードリーダー【実装・カスタマイズ編】 - Qiita
- WebサイトからスマートフォンのカメラでQRコードを読み取る – JavaScript (Android、iOS、PC対応) – flow of water
- バーコード読み取り | KuJunDev
- Javascriptのバーコードライブラリ - Qiita
- PWAでカメラを使うためiOSとAndroidで異なるmanifestを読み込む - Qiita