Love澤's Room

技術系ネタをまとめていたブログ。カテゴリにこだわらず更新していこうかと思います。

ブラウザでバーコードスキャン機能を実装

最近、いろいろと調べながら対応したので、メモしておきます。

今回やったこと

  • ブラウザ上でバーコードスキャン機能を実装
  • モーダル画面を表示するとカメラをオンにしてスキャン開始
  • モーダル画面を閉じるとスキャン終了してカメラをオフ
  • モーダル画面を開いたボタンに応じて、スキャン結果の返却先を変更
  • VB.NET (.NET Framework)環境で実装
f:id:love_zawa:20210326181526p:plainf:id:love_zawa:20210326181333p:plain
バーコードスキャン用のモーダル画面を表示する様子。※読み込んでいる画像は念のため隠しています。

使用したライブラリやフレームワーク

  • QuaggaJS
    • バーコードスキャン機能を提供してくれるライブラリ
    • QRコードには非対応
  • BootStrap 5
    • 画面全体のスタイル調整
    • モーダル画面の表示にも活用

ソース

HTML部分(一部抜粋):sample.aspx
<!doctype html>
<html lang="ja">
<head runat="server">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>バーコードスキャンテスト</title>
    <%--BootstrapのCSS読み込み--%>
    <link rel="stylesheet" type="text/css" href="../../Css/Bootstrap/bootstrap.min.css" />
    <%--画面のCSS読み込み--%>
    <link rel="stylesheet" type="text/css" href="../../Css/Sample/sample.css" />
    <%--jQuery読み込み--%>
    <script src="../../Script/jQuery/jquery-3.6.0.min.js"></script>
</head>
<body>
    <%--検索条件--%>
    <div id="KensakuArea" class="container">
        <div class="row">
            <div class="col-auto">
                <table>
                    <tr>
                        <%--バーコード①--%>
                        <td>
                            <%--モーダルウィンドウの結果を設定するためにIDモードをStaticにしている--%>
                            <asp:TextBox ID="txtBarcode1" runat="server" ClientIDMode="Static" placeholder="バーコード①" CssClass="form-control form-control-sm"/>
                        </td>
                        <td>
                            <%--バーコードスキャン用モーダル画面の表示--%>
                            <%--ASPタグを利用するとPostBackが生じてモーダル画面が表示後即座に消えてるため、あえて通常のタグを利用している--%>
                            <button type="button" style="width:30px; margin:0px; padding:0px; border:none;" data-bs-toggle="modal" data-bs-target="#staticBackdrop" data-textboxid="txtBarcode1">
                                <img src="../../Img/camera.png" style="width: 30px;"/>
                            </button>
                        </td>
                        <%--バーコード②--%>
                        <td>
                            <%--モーダルウィンドウの結果を設定するためにIDモードをStaticにしている--%>
                            <asp:TextBox ID="txtBarcode2" runat="server" CssClass="form-control form-control-sm" ClientIDMode="Static"/>
                        </td>
                        <td>
                            <%--バーコードスキャン用モーダル画面の表示--%>
                            <%--ASPタグを利用するとPostBackが生じてモーダル画面が表示後即座に消えてるため、あえて通常のタグを利用している--%>
                            <button type="button" style="width:30px; margin:0px; padding:0px; border:none;" data-bs-toggle="modal" data-bs-target="#staticBackdrop" data-textboxid="txtBarcode2">
                                <img src="../../Img/camera.png" style="width: 30px;"/>
                            </button>
                        </td>
                    </tr>
                </table>
            </div>
            <%--モーダルウィンドウ(開始)--%>
            <div class="modal fade" id="staticBackdrop" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
                <div class="modal-dialog modal-lg modal-fullscreen-lg-down">
                    <div class="modal-content">
                        <%--タイトル--%>
                        <div class="modal-header">
                            <h5 class="modal-title" id="staticBackdropLabel">バーコードスキャン</h5>
                            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                        </div>
                        <%--読み取り部--%>
                        <div class="modal-body text-center" style="margin: 0px auto;">
                            <%--呼び出し元から渡された引数を保持するHiiden項目--%>
                            <div style="visibility:visible;">
                                <input type="hidden" id="targetTextBoxID">
                            </div>
                            <div>
                                <input type="text" id="scanResult" placeholder="読み取り結果">
                            </div>
                            <%--photo-area部分にカメラの映像を表示--%>
                            <div id="photo-area" class="viewport"></div>
                        </div>
                        <%--フッター--%>
                        <div class="modal-footer">
                            <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">キャンセル</button>
                            <button type="button" id="btnDone" class="btn btn-primary">決定</button>
                        </div>
                    </div>
                </div>
            </div>
            <%--モーダルウィンドウ(終了)--%>
        </div>
    </div>
    <%--QuaggaJS読み込み--%>
    <script type="text/javascript" src="../../Script/QuaggaJS/quagga.js"></script>
    <%--画面用のScript読み込み--%>
    <script type="text/javascript" src="../../Script/Sample/sample.js"></script>
    <%--BootstrapのScript読み込み--%>
    <script src="../../Script/Bootstrap/bootstrap.bundle.min.js"></script>
</body>
</html>
CSSファイル(sample.css)
/*********************************
 * バーコード用モーダル
 * canvasやvideo等のHTML上にないタグはQuaggaJSから生成される
 *********************************/
#photo-area.viewport canvas, video {
    margin: 10px auto;
}
#photo-area.viewport canvas.drawingBuffer, video.drawingBuffer {
    margin-left: -640px;
}
JSファイル(sample.js)
//===================================================================================
// モーダル画面の表示・親画面への値返却
//===================================================================================
// モーダル画面を開いた時の処理
$('#staticBackdrop').on('show.bs.modal', function (event) {
    // モーダルを開いたボタンを取得
    var button = $(event.relatedTarget);
    // data-textboxidの値取得
    var targetTextBoxID = button.data('textboxid');
    // モーダルを取得
    var modal = $(this);
    // 受け取った値をspanタグのとこに保持
    modal.find('.modal-body #targetTextBoxID').text(targetTextBoxID);
    // スキャン開始
    console.log("start");
    startScanner();
});

// モーダルの「×」や「キャンセル」ボタンを押した時の処理
$('#staticBackdrop').on('hidden.bs.modal', function (event) {
    // モーダル閉じる
    $('#staticBackdrop').modal('hide');
    // スキャン終了
    console.log("stop");
    Quagga.stop();
});

// モーダルの「決定」ボタンを押した時の処理
$("#btnDoneScan").on('click', function () {
    // モーダル閉じる
    $('#staticBackdrop').modal('hide');
    // モーダル画面で設定した値を取得し変数に保存
    var scanResultVal = $('#scanResult').val();
    var targetTextBoxID = $('#targetTextBoxID').text();
    // 変数保存済みの読み込み結果をクリア
    $('#scanResult').val("");
    // 呼び出し元のボタンに応じたテキストボックスに設定
    switch (targetTextBoxID) {
        case 'txtBarcode1':
            $("#txtBarcode2").val(scanResultVal);
            break;
        case 'txtBarcode2':
            $("#txtBarcode2").val(scanResultVal);
    }
    // スキャン終了
    console.log("stop");
    Quagga.stop();
});

//===================================================================================
// バーコード用
//===================================================================================
const startScanner = () => {
    Quagga.init({
        // 設定
        inputStream: {
            name: "Live",
            type: "LiveStream",
            target: document.querySelector('#photo-area'),
            constraints: {
                decodeBarCodeRate: 3,
                successTimeout: 500,
                codeRepetition: true,
                tryVertical: true,
                frameRate: 15,
                width: 640,
                height: 480,
                
                facingMode: "environment"
            }
        },
        // デコードタイプ(複数指定可能)
        decoder: {
            readers: [
                "code_39_reader"
            ]
        }
    }, function (err) {
        if (err) {
            console.log(err);
            return;
        }
        console.log("Initialization finished. Ready to start");
        Quagga.start();
        // Set flag to is running
        _scannerIsRunning = true;
    });

    /**
     * Quagga.onProcessed(callback)
     * 処理が完了した後にフレームごとに呼び出される関数callback(data)を登録。
     **/
    Quagga.onProcessed(function (result) {
        var drawingCtx = Quagga.canvas.ctx.overlay,
            drawingCanvas = Quagga.canvas.dom.overlay;

        if (result) {
            // console.log("processing");
            // 検出中の緑の線の枠
            if (result.boxes) {
                //drawingCtx.clearRect(0, 0, parseInt(drawingCanvas.getAttribute("width")), parseInt(drawingCanvas.getAttribute("height")));
                drawingCtx.clearRect(0, 0, drawingCanvas.width, drawingCanvas.height);
                result.boxes.filter(function (box) {
                    return box !== result.box;
                }).forEach(function (box) {
                    Quagga.ImageDebug.drawPath(box, {
                        x: 0,
                        y: 1
                    }, drawingCtx, {
                            color: "green",
                            lineWidth: 2
                        });
                });
            }
            // 検出に成功した瞬間の青い線の枠
            if (result.box) {
                console.log("success");
                Quagga.ImageDebug.drawPath(result.box, {
                    x: 0,
                    y: 1
                }, drawingCtx, {
                        color: "#00F",
                        lineWidth: 2
                    }
                );
            }
            // 検出に成功した瞬間の水平の赤い線
            if (result.codeResult && result.codeResult.code) {
                Quagga.ImageDebug.drawPath(result.line, {
                    x: 'x',
                    y: 'y'
                }, drawingCtx, {
                        color: 'red',
                        lineWidth: 3
                    }
                );
            }
        }
    });

    //barcode read call back
    Quagga.onDetected(function (result) {
        // 取得時の画像を表示
        var resultImg = document.querySelector(".resultImg");
        //resultImg.setAttribute("src", this.Quagga.canvas.dom.image.toDataURL());
        var resultCode = result.codeResult.code;
        // モーダルテキストボックスに表示
        $('#scanResult').val(resultCode);
        console.log("stop");
        Quagga.stop();
    });
}

参考サイト

QuaggaJSの使い方について

メッチャ参考になりました。
ameblo.jp

BootStrapでのモーダル画面について

getbootstrap.jp
qiita.com

ASP.NET が自動生成するDOMのクライアント側での操作について

aspnet.keicode.com