基于 ZXing 的 Vue 在线二维码扫描器实现

简介: 这是一款基于ZXing的在线二维码扫描工具,支持图片上传识别与摄像头实时扫描双模式。识别结果自动归入列表,支持一键复制。采用Nuxt SSR优化,解码器仅在客户端初始化,避免服务端报错;摄像头模式内置2秒去重机制,防止重复识别刷屏。

这篇只讲功能层 JavaScript:同一个扫描器同时支持“图片上传识别”和“摄像头实时识别”,识别到的内容进入结果列表,并提供复制能力。

在线工具网址:https://see-tool.com/qrcode-scanner
工具截图:
工具截图.png

识别依赖 ZXing(@zxing/library)的 BrowserMultiFormatReader,主要用到两种解码方式:

  • 图片:decodeFromImageElement(img)
  • 摄像头:decodeFromVideoDevice(deviceId, videoEl, callback)

下面按功能模块拆开讲核心实现。

1)解码器初始化:SSR 下只在客户端创建

Nuxt 有 SSR,setup 会先在服务器执行一次生成 HTML。服务器环境没有 window / navigator,也没有 navigator.mediaDevices 这类摄像头 API;而 BrowserMultiFormatReader 属于浏览器侧解码器,如果在服务端阶段创建,就可能触发 window is not defined / navigator is undefined 这类错误。

处理方式:把初始化放进 onMounted(只在浏览器端执行),并用 process.client 再兜底一次。

import {
    onMounted, onUnmounted } from "vue";
import {
    BrowserMultiFormatReader } from "@zxing/library";

let codeReader = null;

onMounted(() => {
   
  if (process.client) {
   
    codeReader = new BrowserMultiFormatReader();
  }
});

onUnmounted(() => {
   
  // 离开页面时释放摄像头相关资源
  if (codeReader) codeReader.reset();
});

reset() 用于停止当前扫描流程,并释放视频流相关资源(切换模式或离开页面时会用到)。

2)上传识别:File -> DataURL -> Image -> decode

上传和拖拽统一走 handleFiles(files):遍历文件,先过滤非图片,再逐个触发识别。

const handleFiles = (files) => {
   
  if (!files || files.length === 0) return;

  Array.from(files).forEach((file) => {
   
    if (!file.type.startsWith("image/")) {
   
      addResult(file.name, "仅支持图片文件", "error");
      return;
    }
    scanImageFile(file);
  });
};

scanImageFile 的流程是把文件读成 DataURL,加载成 Image,再交给 ZXing 解码:

const scanImageFile = (file) => {
   
  if (!codeReader) return;

  const reader = new FileReader();
  reader.onload = (e) => {
   
    const img = new Image();
    img.onload = () => {
   
      codeReader
        .decodeFromImageElement(img)
        .then((result) => addResult(file.name, result.text, result.format))
        .catch(() => addResult(file.name, "未识别到二维码", "error"));
    };
    img.src = e.target.result;
  };
  reader.readAsDataURL(file);
};

这里使用 Image() 的原因:先让浏览器把 DataURL 解码成像素数据,再由 ZXing 从像素中定位并识别二维码。

3)摄像头识别:decodeFromVideoDevice 持续回调

摄像头模式不自行做 getUserMedia + canvas 截帧,而是让 ZXing 直接接管:它会持续从视频帧中尝试识别。

const isCameraActive = ref(false);
const videoElement = ref(null);

const startCamera = () => {
   
  if (!codeReader) return;
  isCameraActive.value = true;

  codeReader
    .decodeFromVideoDevice(null, videoElement.value, (result, err) => {
   
      if (result) {
   
        addResult("摄像头扫描", result.text, result.format);
      }
      // 识别不到时 err 往往只是“没找到”,不需要每帧都弹提示
    })
    .catch(() => {
   
      isCameraActive.value = false;
      addResult("摄像头", "摄像头启动失败或无权限", "error");
    });
};

const stopCamera = () => {
   
  if (codeReader) codeReader.reset();
  isCameraActive.value = false;
};

null 表示用默认摄像头;如果你自己做了设备选择,把 deviceId 传进去就行。

4)结果结构:只存“来源 + 内容 + 格式 + 时间”

结果列表使用数组保存,元素结构如下:

// { source, content, format, isError, timestamp }
const results = ref([]);

字段都很直白:来源是“文件名/摄像头”,content 是解出来的文本,format 用来展示二维码类型,timestamp 用来做去重。

5)为什么要去重:摄像头会反复识别同一张码

摄像头模式下,二维码只要还在画面里,就可能被重复识别(可以理解为间隔很短就会再次识别)。如果每次识别成功都写入结果列表,会出现大量重复记录。

这里用“时间窗口去重”:2 秒内内容相同则跳过写入。

const addResult = (source, content, format) => {
   
  const isError = format === "error";
  const now = Date.now();

  const recentSame = results.value.find(
    (r) => r.content === content && now - r.timestamp < 2000,
  );

  if (recentSame && !isError) return;

  let formatName = format;
  if (!isError && typeof format === "number")
    formatName = getFormatName(format);
  else if (format && format.formatName) formatName = format.formatName;

  results.value.unshift({
   
    source,
    content,
    format: isError ? "" : String(formatName),
    isError,
    timestamp: now,
  });
};

效果是:镜头对准二维码时,结果只会稳定新增一次,不会被重复记录刷屏。

6)格式显示:把枚举值映射成常见名字

ZXing 的 format 有时候是枚举数字。为了让展示更直观,这里做一个映射表,把常见值转成字符串。

const getFormatName = (format) => {
   
  const formats = {
   
    11: "QR_CODE",
    5: "DATA_MATRIX",
    0: "AZTEC",
    10: "PDF_417",
  };
  return formats[format] || format;
};

没覆盖到的就原样返回,至少信息不会丢。

目录
相关文章
|
3月前
|
安全 算法 项目管理
日期计算器在线工具分享
这是一款基于Vue3开发的免费在线日期计算器,支持日期加减、天数差值、工作日计算及星期查询。界面简洁、操作便捷、结果精准,所有计算均在本地完成,保障隐私安全,无需注册即可跨设备使用。
1024 4
日期计算器在线工具分享
|
1月前
|
JavaScript 测试技术 索引
正则表达式测试 在线工具分享
分享一款实用的在线正则表达式测试工具,支持实时匹配高亮、分组详情查看、g/i/m标志切换、常用模板一键插入及正则替换功能,助你高效验证、提取与处理文本,是开发与学习正则的得力助手!
392 10
|
3月前
|
JavaScript 安全 前端开发
文本编码转换器在线工具分享
推荐一款基于Vue.js开发的在线文本编码转换器:支持12种格式互转(Base64、Unicode、UTF-8 Hex、HTML实体等),实时双向转换、自定义分隔符/前缀,纯前端运行,数据不上传,无广告,安全高效。
374 8
文本编码转换器在线工具分享
|
1月前
|
JavaScript 安全
在线二维码扫描器分享
无需手机,网页秒扫二维码!支持图片上传/摄像头实时识别,本地处理不传图,安全快捷。兼容QR码、Data Matrix等,可读链接、文本、Wi-Fi信息等。适合截图识别、电脑端快速读码、临时扫码等场景。
264 3
|
1月前
|
JavaScript 前端开发 API
文本行过滤/筛选 在线工具核心JS实现
这是一个轻量级在线文本行过滤工具:支持按关键词(含正则)、模糊/精确匹配,可灵活设置“保留/删除”匹配行,并支持多条件OR/AND组合。逻辑分层清晰,前端负责交互,核心引擎专注计算,提供复制、下载功能。
70 6
文本行过滤/筛选 在线工具核心JS实现
|
2月前
|
缓存 JavaScript 前端开发
Vue3二维码生成器实现方案
本文拆解 Vue3/Nuxt3 二维码生成器实战方案,采用「Vue 管结构与状态、独立 JS 负责 Canvas 绘制」的分层架构。通过 `data-*` 属性定义交互协议,实现类型切换、表单联动、实时预览及 PNG/SVG 导出,兼顾可维护性与跨项目复用性。
179 10
Vue3二维码生成器实现方案
|
1月前
|
JavaScript
文件合并拆分 在线工具分享
一款基于Vue 3/Nuxt 3开发的在线文件合并拆分工具,支持按行数或大小拆分大文本,也支持多文件顺序合并并自定义分隔符。无需安装,网页即用,适配手机与电脑,助你高效处理日志、名单、CSV等文本任务。
305 3
|
3月前
|
存储
科学计算器在线工具分享
这款在线科学计算器支持键盘与鼠标双操作,具备AC、M+/M-/MR/MC存储功能,可切换角度/弧度制,一键生成随机数,界面简洁易用,无需安装即开即用。
435 3
科学计算器在线工具分享
|
3月前
|
数据采集 算法 API
Vue3进制转换器实现方案
这是一个支持二、八、十、十六进制实时同步转换的在线工具。采用十进制中转策略,内置防死锁、数据清洗、正则校验与格式化显示(前缀/分组),并支持2–36进制自定义转换。代码简洁健壮,兼顾实用性与可维护性。
162 0
Vue3进制转换器实现方案