基于 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;
};

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

目录
相关文章
|
21天前
|
SQL 机器学习/深度学习 存储
NL2SQL 目前有什么突破?
本文梳理NL2SQL十年演进:从Seq2SQL到大模型Prompt工程,总结Schema链接、结构预测、少样本提示与自我修正四大突破,单表准确率达85–90%;但多表JOIN仍卡在≤70%瓶颈。进而对比字节宽表方案与Palantir/UINO本体智能体路线,揭示下一代技术选型关键。
|
3天前
|
JavaScript Linux API
零基础玩转OpenClaw部署及避坑指南:从阿里云到本地多系统搭建,附千问大模型API配置与免费模型接入方案
OpenClaw(原Clawdbot)作为2026年热门的开源AI执行网络,凭借其强大的任务执行与大模型集成能力,成为开发者与企业用户的首选AI工具之一。本文将详细介绍2026年阿里云轻量服务器部署及本地MacOS/Linux/Windows11部署OpenClaw的完整步骤,同时涵盖阿里云千问大模型API配置与免费大模型Coding Plan API配置方法,并针对部署过程中的常见问题提供解决方案,帮助用户快速完成OpenClaw的部署与使用。
271 4
|
3月前
|
安全 算法 项目管理
日期计算器在线工具分享
这是一款基于Vue3开发的免费在线日期计算器,支持日期加减、天数差值、工作日计算及星期查询。界面简洁、操作便捷、结果精准,所有计算均在本地完成,保障隐私安全,无需注册即可跨设备使用。
897 4
日期计算器在线工具分享
|
14天前
|
安全 前端开发 NoSQL
基于Spring Boot+Vue的中西医一体化诊所HIS源码,具备高安全性、模块化设计与易扩展性
云诊所系统是基于Spring Boot+Vue的中西医一体化HIS源码,支持电子病历、处方管理、药房进销存、医保结算及会员服务。具备高安全性、模块化设计与易扩展性,已落地百余项目,适配社区卫生站、门诊部等基层医疗机构。
86 6
|
16天前
|
JavaScript 前端开发 算法
基于 pdf-lib 的图片转PDF工具核心JS实现
这是一款纯前端图片转PDF工具:基于Vue构建交互界面,利用开源库pdf-lib在浏览器端完成PDF生成。支持图片上传、排序、旋转、自定义页面尺寸与布局(contain/cover/center),全程无需后端,所有处理均在本地完成,安全高效。
120 6
|
22天前
|
并行计算 算法 机器人
水声通信MATLAB代码大全
水声通信MATLAB代码大全
107 2
|
22天前
|
Arthas 人工智能 Java
我们做了比你更懂 Java 的 AI-Agent -- Arthas Agent
Arthas Agent 是基于阿里开源Java诊断工具Arthas的AI智能助手,支持自然语言提问,自动匹配排障技能、生成安全可控命令、循证推进并输出结构化报告,大幅降低线上问题定位门槛。
743 64
我们做了比你更懂 Java 的 AI-Agent -- Arthas Agent
|
4天前
|
人工智能 安全 开发者
🔈大模型玩家「合体」指南:知乎 × 魔搭社区 账号绑定功能正式上线啦
知乎与魔搭(ModelScope)账号正式打通!开发者可一键绑定,同步展示开源模型、数据集至知乎主页,实现“实践+讨论”双链路表达。绑定后还能加入圈子赢限定勋章及周边!
102 21
|
1月前
|
JavaScript 测试技术 索引
正则表达式测试 在线工具分享
分享一款实用的在线正则表达式测试工具,支持实时匹配高亮、分组详情查看、g/i/m标志切换、常用模板一键插入及正则替换功能,助你高效验证、提取与处理文本,是开发与学习正则的得力助手!
287 10