🔍Web(H5)中"扫描/识别"条形码或二维码的方案(quagga/zxing/qr-scanner)

6,352 阅读11分钟

写在开头

Hello,各位UU好呀! 😀

在日常生活中,条形码(一维条码)和二维码随处可见,它们作为信息的高效载体,广泛渗透于各行各业,深度融入了我们生活的方方面面。而今天,咱们要一同探寻在 Web 技术中,该如何 "使用" 条形码与二维码。

这个 "使用" 咱们侧重考虑两个方向:其一,用户能主动通过扫码设备(手机/电脑的摄像头)扫描条码或二维码,快速获取背后隐藏的信息;其二,用户能通过上传一张条码或者二维码的图片,咱们也能识别读取码到背后的信息。一般能做到这两点就足够满足咱们日常项目的实际需求了。

那么,本次要分享的就是关于扫描/识别条形码或二维码的三种插件,请诸君按需食用哈。

类型说明

条形码

  • EAN 码:包括 EAN-8 和 EAN-13,是国际通用的商品条码,主要应用于零售业,其中 EAN-8 长度为 8 位,EAN-13 长度为 13 位,所表达的信息全部为数字。
  • Code 128 码:是一种广泛应用于企业内部管理、生产流程、物流控制系统等方面的高密度条码码制。它可以表示从 ASCII 0 到 ASCII 127 共 128 个字符,包括数字、字母和符号字符。有三种不同的版本:a(数字、大写字母、控制字符)、b(数字、大小字母、字符)、c(纯数字,双位数字)。
    1. CODE128:只能包含ASCII字符(数字、大小写字母及常用符号)
    2. CODE128A:只能包含数字、大写字母及部分特殊字符(不能包含小写字母)
    3. CODE128B:只能包含数字、大小写字母及部分特殊字符
    4. CODE128C:只能由数字组成,并且必须是偶数个数字
  • Code 39 码:可表示数字、英文字母以及 “−”、“.”、“/”、“+”、“%”、“$”、“ ”(空格)和 “*” 共 44 个符号,其中 "*" 仅作为起始符和终止符,能根据需要确定条码的长度和信息,主要应用于工业生产线领域、图书管理等。
  • Code 93 码:类似于 Code 39 码,但密度较高,能够替代 Code 39 码,可编码的字符范围与 Code 39 码类似。
  • UPC 码:仅由数字组成,有 UPC-A、UPC-C、UPC-E 等类型,常用于北美地区的商品标识,UPC-A、UPC-C 长度为 12 位,UPC-E 长度为 8 位,与 EAN 码类似。
  • 12of5/2of5:也称为交叉 25 码,主要应用于包装、运输以及国际航空系统的机票顺序编号等,其编码信息为 0-9 数字字符,长度理论上没有限制。
  • Codabar 码:应用于血库、图书馆、包裹等的跟踪管理,其编码范围为 0-9 数字字符及 “a”、“b”、“c”、“d”、“-”、“.”、“/”、“:”、“+”、“$”,开始和结尾字符必须是 “a”、“b”、“c”、“d” 中的字符,长度理论上没有限制。
  • MSI 码:编码信息为 0-9 数字字符,长度理论上没有限制。
  • Postnet 码:用于美国邮政服务的条码,编码信息为 0-9 数字字符,长度为 5 位、9 位或者 11 位。
  • Matrix 25 码: 支持的范围为 0-9 数字字符,长度为 13 位。
  • ISBN 码:用于图书管理的专用条码。
  • ITF/ITF14:只能由数字组成,并且必须是偶数个数字。ITF14 由14位数字组成。

二维码

  • QR 码:是最常见的二维码类型,具有存储容量大、高密度、容错能力强等特点,适用于各种场景,如支付、链接、名片等。
  • Data Matrix 码:是一种方形的二维码,适合在空间有限的情况下使用,存储容量相对较小,但可存储大量数据,常用在工业标识、电子零部件标识等领域。
  • Aztec 码:具有较高的编码效率和容错能力,常用于移动设备应用、票务和广告等领域。
  • PDF417 码:是一种条形码式的二维码,可以存储大量数据,适用于需要存储大量信息的场景,如驾驶证、身份证等。
  • MaxiCode 码:主要用于物流和仓储管理,具有大容量、高速度和可靠性等特点。
  • Code 49 码:属于堆叠式二维码,可编码大量信息,常用于物流、仓储和文档管理等领域。
  • Code 16K 码:也是一种堆叠式二维码,能够存储较多的数据,适用于需要对大量信息进行编码的场景。
  • Vericode 条码:具有高精度和高速度的识别特点,常用于电子制造、医疗和航空航天等领域。
  • CP 条码:一种矩阵式二维码,具有较好的纠错能力和可读性,适用于工业自动化、物流和零售等领域。
  • Codablock F 条码:可以编码大量的字符和数字信息,常用于物流、运输和文档管理等领域。
  • 田字码:是一种具有中国特色的二维码,具有较高的编码密度和纠错能力,适用于各种信息的编码和识别。
  • UltraCode 条码:具有大容量、高速度和可靠性等特点,适用于物流、仓储和文档管理等领域。

条形码与二维码的各种类型还是挺多的😋,有些小编也是问 AI 才知道的,Em...大概过一遍就行,排靠前面的类型会比较常用一点。

"chen_mou_ren" 字符串通过 Code 128 码生成的条形码,后续测试使用:

橙某人_条形码.png

"chen_mou_ren" 字符串通过 QR 码生成的二维码,后续测试使用:

橙某人_二维码.png

quagga

quagga 是一款完全用 Javascript 编写的条形码类库,基于 HTML5 的 canvasMediaDevices API 等实现扫码功能,注意它侧重的是条形码,仅支持条形码的扫描与识别。

支持的类型有:EANCODE 128CODE 39EAN 8UPC-AUPC-CI2of52of5CODE 93CODABAR

Github:传送门

官方文档:传送门

在线案例:传送门1传送门2

咱们来尝试做一个案例玩玩,这里小编选用 Vue3+Vite 的形式作为演示。

初始化项目:

npm create vue@latest

安装 quagga 插件:

npm install quagga -S

再安装 @vitejs/plugin-basic-ssl 插件:

npm install @vitejs/plugin-basic-ssl -D

由于一会要在手机上打开页面(H5)进行真机测试,咱们需要满足三个条件,其一,让你的电脑与手机处于同一个局域网下,你可以直接让电脑通过 WIFI 连接你手机的热点就行。其二,关闭电脑的防火墙。其三,电脑本地所启动的服务必须得是 HTTPS 服务,@vitejs/plugin-basic-ssl 插件能帮咱们做这件事情。

修改 vite.config.js 配置文件:

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import basicSsl from "@vitejs/plugin-basic-ssl";

export default defineConfig({
    plugins: [
        vue(),
        // 启动HTTP服务
        basicSsl()
    ],
    server: {
        // 通过ip启动
        host: "0.0.0.0",
        port: 4000,
        cors: true,
    },
});

启动项目:

npm run dev

然后,你先试试通过 IP 的形式,瞧瞧在手机浏览器上能不能正常打开电脑的页面。

image.png

因为测试的案例代码比较简单,咱就直接贴代码瞧瞧,App.vue 文件:

<template>
    <h1>橙某人</h1>
    <button @click="scanner">扫码</button>&nbsp;
    <button @click="close">关闭</button>&nbsp;
    <div class="container">
        <!-- 可以自行提供video标签,也可以不提供,会自动生成 -->
        <!-- <video></video> -->
    </div>
    <h4>识别到的信息:<span style="color: red;">{{ message }}</span></h4>
</template>

<script setup>
import { ref } from "vue";
import Quagga from "quagga";

let message = ref('');

function scanner() {
    Quagga.init(
        {
            // 输入流,定义图形/视频来源
            inputStream: {
                // LiveStream(默认)/ImageStream/VideoStream
                type: "LiveStream",
                target: document.querySelector(".container"),
            },
            // 解码器
            decoder: {
                // 解码器类型: code_128_reader/code_39_reader/upc_reader/i2of5_reader/...
                readers: ["code_128_reader"],
            },
        },
        (err) => {
            if (err) {
                alert('请开启该浏览器的相机权限并重新进行加载。');
                return;
            };
            Quagga.start();
        }
    );
}

// 不断检测条形码位置,为图像/视频每帧不断进行调用
Quagga.onProcessed(result => {
    // 绘制实际检测的捕获框框
    const drawingCtx = Quagga.canvas.ctx.overlay;
    const drawingCanvas = Quagga.canvas.dom.overlay;
    // 下面会实时绘制绿色框、蓝色框、红色线三种图形,你可以对照下方的GIF图进行对比
    if (result) {
        // 绿色框
        if (result.boxes) {
            drawingCtx.clearRect(0, 0, parseInt(drawingCanvas.getAttribute("width")), parseInt(drawingCanvas.getAttribute("height")));
            result.boxes.filter(box => {
                return box !== result.box;
            }).forEach(box => {
                Quagga.ImageDebug.drawPath(box, {x: 0, y: 1}, drawingCtx, {color: "green", lineWidth: 2});
            });
        }
        // 蓝色框
        if (result.box) {
            Quagga.ImageDebug.drawPath(result.box, {x: 0, y: 1}, drawingCtx, {color: "blue", lineWidth: 2});
        }
        // 红色线
        if (result.codeResult && result.codeResult.code) {
            Quagga.ImageDebug.drawPath(result.line, {x: 'x', y: 'y'}, drawingCtx, {color: 'red', lineWidth: 3});
        }
    }
});

// 正确定位并解码到条形码时触发
Quagga.onDetected(result => {
    const code = result.codeResult.code;
    if (message.value !== code) {
        message.value = code;
    }
});

function close() {
    Quagga.stop();
}
</script>

<style>
.container {
    width: 100%;
    height: 60vh;
    position: relative;
    margin-top: 10px;
}
.container > video {
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.container > canvas.drawingBuffer {
    position: absolute;
    left: 0;
    top: 0;
    max-width: 100%;
    width: 100%;
    height: 100%;
}
</style>

inputStream.type 值说明:

  • LiveStream:表示从摄像头等实时视频流设备获取图像数据进行扫描。例如,在移动设备上使用浏览器扫描二维码时,就是通过获取摄像头的实时视频流来进行的。
  • ImageStream:用于从静态图片文件中获取图像数据进行扫描。
  • VideoStream:用于从视频文件中获取图像数据进行扫描。

效果如下:

20241129-3.gif

主动 "扫描" 的功能这样子就基本完成了,咱们再来瞧瞧上传 "识别" 的功能要如何做。😁

代码量不多,直接贴上来瞧瞧:

<template>
    <!-- ... -->
    <br />
    <input type="file" @change="fileChange">
</template>

<script setup>
// ...

function fileChange(event) {
    const file = event.target.files[0];
    const fileURL = URL.createObjectURL(file);
    // 该方法能对单个图像或视频帧进行解码
    Quagga.decodeSingle({
        decoder: {
            readers: ["code_128_reader"]
        },
        locate: true, // 在图像/视频中定位条形码
        src: fileURL // 或者是 base64
    }, result => {
        if(result.codeResult) {
            console.log("识别到的信息:", result.codeResult.code);
        } else {
            console.log("未识别到条形码");
        }
    });
}
</script>

Quagga.decodeSingle(config, callback) 该方法能对单个图像或视频帧进行解码,识别出图像/视频中的条形码。还有,在使用该方法时,一样会触发 Quagga.onProcessedQuagga.onDetected 等方法的执行。

效果:

image.png

zxing

zxing 是一款由用 Typescript 编写的一维/二维条码图像处理库,即条形码与二维码,它是由 Java 版本 ZXing 库移植而来的。

支持的类型有:

  • 条形码:UPC-AUPC-EEAN-8EAN-13Code 39Code 93Code 128CodabarITFRSS-14
  • 二维码:QR CodeData MatrixAztecPDF 417

Github:传送门

在线案例:传送门

同样,咱们也来做一个小案例体验一下,继续沿用上面的 Vue3 项目。

安装 @zxing/library 插件:

npm install @zxing/library -S

App.vue 文件:

<template>
    <h1>橙某人</h1>
    <button @click="scanner">扫码</button>&nbsp;
    <button @click="close">关闭</button>&nbsp;
    <div class="container">
        <video id="video"></video>
        <div v-if="mask" class="mask"></div>
    </div>
    <h4>识别到的信息:<span style="color: red;">{{ message }}</span></h4>
</template>

<script setup>
import { onMounted, ref } from "vue";
import { BrowserMultiFormatReader } from '@zxing/library';

let message = ref('');
let codeReader = null;
let selectedDeviceId = '';
let mask = ref(false);

function init() {
    // 识别和处理多种常见的条形码和二维码格式
    codeReader = new BrowserMultiFormatReader();
    // 获取当前设备上可用的视频输入设备列表
    codeReader.getVideoInputDevices().then(videoInputDevices => {
        if (videoInputDevices.length > 1) {
            // 后缀摄像头(手机)
            selectedDeviceId = videoInputDevices[1].deviceId;
        }else {
            // 前置摄像头
            selectedDeviceId = videoInputDevices[0].deviceId;
        }
    })
}

onMounted(() => {
    init();
})

function scanner() {
    mask.value = true
    // 自动打开指定的视频输入设备,并实时对视频流中的每一帧图像进行条形码和二维码的解码操作,此方法不是只进行一次解码尝试,而是在视频流持续传输的过程中,不断地对每一帧图像进行解码分析
    codeReader.decodeFromVideoDevice(selectedDeviceId, 'video', (result, err) => {
        if (result) {
            message.value = result.text;
        }
        if (err) {}
    })
}
function close() {
    codeReader.reset();
    message.value = '';
    mask.value = false;
}
</script>

<style>
.container {
    width: 100%;
    height: 60vh;
    position: relative;
    margin-top: 10px;
    overflow: hidden;
}
.container > video {
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.mask {
    position: absolute;
    left: 15%;
    top: 20%;
    max-width: 100%;
    width: 70%;
    height: 60%;
    border-radius: 2px;
    outline: rgba(0, 0, 0, .25) solid 20vmax;
}
</style>

效果:

20241129-5.gif

再来瞧瞧上传识别的情况:

function fileChange(event) {
    const file = event.target.files[0];
    const reader = new FileReader();
    reader.onload = (e) => {
        const img = new Image();
        img.onload = () => {
            const barcodeReader = new BrowserMultiFormatReader();
            // 从图像元素中解码二维码或条形码
            barcodeReader.decodeFromImage(img)
                .then(result => {
                    message.value = result.text;
                })
                .catch(err => {
                    console.error(err);
                });
        };
        img.src = e.target.result;
    };
    reader.readAsDataURL(file);
}

zxing 提供的能力是非常强大的,具有出色的条形码和二维码解码能力,还支持广泛的码类。但是,有些不足的是,相关的 API 并没有文档说明,网上资料也很少,唯一能进行参考的就是看官方提供的在线案例;要不然就得翻其源码😁,不过,还好源码由 TS 编写比较好找就是。

小编下面列举一些关键类,你可以做一个参考:

  • MultiFormatReader:用于读取和解码多种格式的条形码和二维码。它可以根据配置自动识别并尝试解码多种不同格式的编码信息。
  • MultiFormatWriter:用于将数据按照多种条形码和二维码格式进行编码并生成对应的图像。
  • QRCodeReader:用于读取二维码的类。
  • QRCodeWriter:用于生成二维码的类。
  • BarcodeFormat:定义了各种条形码格式的枚举类型,如 EAN-13、UPC-A、Code 128 等,用于标识不同类型的条形码。
  • Code128Reader:用于读取 Code 128 条形码的类。
  • QRCodeWriter:用于生成二维码的类。
  • ...

还有非常之多的类,你可以自行去查阅:传送门

浏览器环境相关的类:

  • BrowserCodeReader:在浏览器中读取条形码和二维码的基础类,它是其他更具体读取器类的基础。
  • BrowserMultiFormatReader:用于在浏览器环境中读取多种格式条形码和二维码。与 MultiFormatReader 差不多,但是,它专门为浏览器环境定制,除解密能力,还能获取摄像头视频流、操作 HTML 的图像元素。
  • BrowserBarcodeReader:用于在浏览器环境中读取条形码的类,可识别多种常见条形码格式。
  • BrowserQRCodeReader:用于在浏览器环境中读取 QR 码的类。
  • BrowserDatamatrixCodeReader:用于在浏览器环境中读取 Data Matrix 码。
  • BrowserAztecCodeReader:用于在浏览器环境中读取 Aztec 码。
  • BrowserPDF417Reader:用于在浏览器环境中读取 PDF417 码。
  • BrowserQRCodeSvgWriter:用于在浏览器环境中创建 QR 码的 SVG 图形。
  • VideoInputDevice:用于管理浏览器中的视频输入设备,如摄像头。它可以获取可用摄像头的列表,选择要使用的摄像头,并处理与摄像头相关的配置和操作。
  • HTMLCanvasElementLuminanceSource:从 HTML 的 Canvas 元素获取图像数据并转换为适合 ZXing 库处理的亮度源相关的类。它负责从 Canvas 元素中提取图像的亮度信息,为后续的条形码和二维码解码提供必要的数据支持,是在利用 Canvas 元素进行图像扫描和识别时的重要中间环节。
  • HTMLVisualMediaElement:浏览器中的可视化媒体元素(如视频元素)相关的类,用于处理从视频流中获取图像数据以及与可视化媒体元素的交互等操作。
  • DecodeContinuouslyCallback:通常作为一个回调函数类型的定义,用于在连续解码过程中处理解码结果或错误。

qr-scanner

qr-scanner 是一款由用 Typescript 编写的轻量级二维码类库,确实够轻,就两个文件组成。

支持的类型有:QR Code

Github:传送门

在线案例:传送门

同样,实践出真理,写案例耍耍。

安装 qr-scanner 插件:

npm install qr-scanner -S

App.vue 文件:

<template>
    <h1>橙某人</h1>
    <button @click="scanner">扫码</button>&nbsp;
    <button @click="close">关闭</button>&nbsp;
    <button @click="turnFlashOn">开启闪光点</button>&nbsp;
    <button @click="turnFlashOff">关闭闪光点</button>
    <div class="video-container">
        <video id="video"></video>
    </div>
</template>

<script setup>
import { ref } from 'vue'
import QrScanner from "qr-scanner";

let qrScanner = null;
let message = ref('')

function scanner() {
    qrScanner = new QrScanner(
        document.getElementById("video"),
        (res) => {
            message.value = res.data;
        },
        {
            onDecodeError(error) {},
            preferredCamera: "environment",
            highlightScanRegion: true,
            highlightCodeOutline: true,
        }
    );
    qrScanner.start()
}
function close() {
    qrScanner.stop();
}

// 开启闪光点
function turnFlashOn() {
    qrScanner.turnFlashOn();
}
// 关闭闪光点
function turnFlashOff() {
    qrScanner.turnFlashOff();

}
</script>

<style>
.video-container {
    width: 100%;
    height: 60vh;
    position: relative;
    margin-top: 10px;
}
video {
    width: 100%;
    height: 100%;
    object-fit: cover;
}
</style>

效果:

20241129-7.gif

上传识别能力:

function fileChange(event) {
    const file = event.target.files[0];
    // 用于扫描给定图像以检测和识别其中的二维码
    QrScanner.scanImage(file, { returnDetailedScanResult: true }).then(result => {
       message.value = result.data;
    }).catch(e => console.error('No QR code found.');
}

qr-scanner 就比较简单啦,它更专注处理二维码的扫描/识别,并且,它提供了完整的文档说明,还有,它也提供了友好的交互效果,这点就挺好!😁

总结

quaggazxingqr-scanner
目标条形码条形码与二维码二维码
出现时间最早2009年2018年
star数5.1k2.5k2.5k
最近一次更新五年前三个月前两年前
语言JSTSTS




至此,本篇文章就写完啦,撒花撒花。

image.png