前端实现二维码生成器:Canvas、SVG、纠错等级、扫码校验与批量

二维码生成看起来只是调用一个库,但如果要用于商品标签、说明书、展会资料或批量生产,还需要解决几个工程问题:

  • 怎样导出清晰的PNG和SVG?
  • 加入Logo后如何保持可扫描?
  • 怎样在下载前自动验证二维码?
  • 如何批量处理CSV数据?
  • 毫米尺寸的标签如何转换成300 DPI图片?
  • 怎样避免重复内容和错误网址进入导出文件?

本文使用 qrcodejsQRJSZip,实现一套完全运行在浏览器中的二维码处理流程。

一、安装依赖

复制代码
npm install qrcode jsqr jszip papaparse

各个依赖的用途如下:

依赖 用途
qrcode 生成Canvas、PNG和SVG二维码
jsQR 从Canvas图像中重新识别二维码
jszip 批量打包二维码文件
papaparse 读取和解析CSV数据

二维码内容、Logo图片和CSV文件都可以直接在浏览器内处理,不需要上传到服务端。

二、生成基础二维码

创建一个Canvas元素:

复制代码
<canvas id="qrCanvas"></canvas>

使用 qrcode 生成二维码:

复制代码
import QRCode from 'qrcode';

const canvas = document.getElementById('qrCanvas');

await QRCode.toCanvas(
  canvas,
  'https://example.com/product/10001',
  {
    width: 512,
    margin: 4,
    errorCorrectionLevel: 'M',
    color: {
      dark: '#111827',
      light: '#ffffff'
    }
  }
);

几个主要参数:

  • width:输出图片宽度
  • margin:二维码四周的安全留白
  • errorCorrectionLevel:纠错等级
  • dark:二维码前景色
  • light:二维码背景色

用于网页和普通文档时,512像素通常已经够用;需要继续加工或者印刷时,可以使用1024或2048像素。

三、纠错等级应该怎么选择?

二维码通常提供四个纠错等级:

等级 大致纠错能力 使用场景
L 约7% 内容较短、显示尺寸较大
M 约15% 普通网址二维码
Q 约25% 二维码可能受到轻微遮挡
H 约30% 带Logo或者印刷环境复杂

纠错等级越高并不意味着二维码一定越好。

在相同内容和尺寸下,提高纠错等级会增加二维码模块数量,使图案更加密集。因此普通二维码可以从M级开始,添加Logo后再考虑使用H级。

四、生成适合印刷的SVG

PNG是位图,放大后可能模糊。商品包装、大尺寸海报和设计排版更适合使用SVG。

复制代码
const svg = await QRCode.toString(
  'https://example.com/product/10001',
  {
    type: 'svg',
    margin: 4,
    errorCorrectionLevel: 'M',
    color: {
      dark: '#111827',
      light: '#ffffff'
    }
  }
);

const blob = new Blob([svg], {
  type: 'image/svg+xml;charset=utf-8'
});

const url = URL.createObjectURL(blob);
const link = document.createElement('a');

link.href = url;
link.download = 'product-10001.svg';
link.click();

URL.revokeObjectURL(url);

SVG可以继续导入Illustrator、Figma等设计工具中排版,同时不会因为放大而失去清晰度。

五、下载前进行扫码检测

"二维码生成成功"和"二维码可以识别"并不是一回事。

自定义颜色、缩小留白、加入Logo或者编码过长内容,都可能导致二维码无法正常扫描。

可以使用 jsQR 对生成后的Canvas进行一次反向识别:

复制代码
import jsQR from 'jsqr';

function verifyQrCanvas(canvas, expectedValue) {
  const context = canvas.getContext('2d');

  const imageData = context.getImageData(
    0,
    0,
    canvas.width,
    canvas.height
  );

  const result = jsQR(
    imageData.data,
    imageData.width,
    imageData.height
  );

  return {
    readable: Boolean(result),
    matched: result?.data === expectedValue,
    decodedValue: result?.data || ''
  };
}

调用检测:

复制代码
const result = verifyQrCanvas(
  canvas,
  'https://example.com/product/10001'
);

if (!result.readable) {
  console.error('二维码无法识别');
} else if (!result.matched) {
  console.error('识别结果与原始内容不一致');
} else {
  console.log('二维码检测通过');
}

这种检测可以发现明显的生成问题,但不能完全替代真实设备测试。

正式印刷前,还要使用最终尺寸、材质和颜色打样,并使用不同手机进行扫描。

六、加入Logo时需要注意什么?

Logo通常放在二维码中心。实现方式是先生成二维码,再把Logo绘制到Canvas上:

复制代码
function drawLogo(canvas, image, scale = 0.16) {
  const context = canvas.getContext('2d');
  const logoSize = canvas.width * scale;

  const x = (canvas.width - logoSize) / 2;
  const y = (canvas.height - logoSize) / 2;

  context.fillStyle = '#ffffff';
  context.fillRect(x - 8, y - 8, logoSize + 16, logoSize + 16);
  context.drawImage(image, x, y, logoSize, logoSize);
}

加入Logo后建议:

  1. 将纠错等级调整为H。
  2. Logo宽度控制在二维码宽度的15%到20%左右。
  3. 不要遮挡三个定位图案。
  4. Logo周围保留浅色背景。
  5. 绘制完成后重新运行扫码检测。

即使检测通过,也不建议把Logo继续无限放大。

七、批量数据需要先校验

批量二维码可以采用以下输入格式:

复制代码
商品A    https://example.com/product-a    product-a
商品B    https://example.com/product-b    product-b
商品C    https://example.com/product-c    product-c

三个字段分别是:

复制代码
名称 + Tab + 二维码内容 + Tab + 文件名

解析后需要检查:

  • 内容是否为空
  • URL格式是否正确
  • 是否为HTTP或HTTPS网址
  • 是否存在重复内容
  • 文件名是否包含非法字符
  • 相同文件名是否发生冲突

简单的文件名清理函数:

复制代码
function safeFilename(value) {
  return value
    .trim()
    .replace(/[<>:"/\\|?*\x00-\x1F]/g, '-')
    .replace(/\s+/g, '-')
    .replace(/-+/g, '-')
    .slice(0, 80);
}

批量处理时,最好保留原始行号。出现问题后,可以快速定位到CSV中的对应记录。

八、批量生成并打包ZIP

使用JSZip可以把生成结果打包下载:

复制代码
import JSZip from 'jszip';
import QRCode from 'qrcode';

async function exportQrZip(rows) {
  const zip = new JSZip();
  const manifest = [];

  for (let index = 0; index < rows.length; index++) {
    const row = rows[index];

    try {
      const dataUrl = await QRCode.toDataURL(row.value, {
        width: 1024,
        margin: 4,
        errorCorrectionLevel: 'M'
      });

      const base64 = dataUrl.split(',')[1];
      const filename = `${safeFilename(row.name)}.png`;

      zip.file(filename, base64, { base64: true });

      manifest.push({
        line: index + 1,
        name: row.name,
        value: row.value,
        filename,
        status: 'success'
      });
    } catch (error) {
      manifest.push({
        line: index + 1,
        name: row.name,
        value: row.value,
        filename: '',
        status: 'failed'
      });
    }
  }

  zip.file(
    'manifest.json',
    JSON.stringify(manifest, null, 2)
  );

  return zip.generateAsync({ type: 'blob' });
}

建议在ZIP中同时加入一份清单,记录:

  • 原始行号
  • 商品名称
  • 二维码内容
  • 导出文件名
  • 生成状态
  • 错误原因

这样可以避免二维码图片与商品数据无法对应。

九、SKU标签的毫米尺寸如何转换?

用于标签打印时,不能只设置Canvas像素,还要考虑实际毫米尺寸和DPI。

换算公式:

复制代码
像素 = 毫米 ÷ 25.4 × DPI

例如生成一张50 × 30毫米、300 DPI的标签:

复制代码
const dpi = 300;
const widthMm = 50;
const heightMm = 30;

const widthPx = Math.round(widthMm / 25.4 * dpi);
const heightPx = Math.round(heightMm / 25.4 * dpi);

console.log(widthPx, heightPx);
// 约为 591 × 354 像素

创建对应Canvas:

复制代码
const canvas = document.createElement('canvas');

canvas.width = widthPx;
canvas.height = heightPx;

随后可以在Canvas中绘制:

  • SKU二维码
  • SKU编码
  • 商品名称
  • 颜色或尺寸变体
  • 品牌名称
  • 标签边框

打印时应保持100%缩放,否则浏览器可能改变实际标签尺寸。

十、实际开发中容易忽略的问题

二维码内容过长

完整网址附带大量查询参数时,二维码会变得非常密集。可以缩短不必要的参数,但不要随意使用无法长期维护的临时短链接。

前景色和背景色对比不足

浅蓝色配白色、灰色配透明背景,可能在屏幕上看起来正常,印刷后却难以识别。

留白被设计稿裁掉

二维码四周需要保留安全区域。设计师在排版时如果把留白裁掉,扫码成功率会明显下降。

只检查第一张二维码

批量任务中,每一条数据的长度都可能不同。第一张可以识别,不代表后面的二维码都能识别。

二维码指向临时页面

二维码印刷以后很难修改,因此目标网址应尽量使用可长期维护的域名和路径。

总结

一个可用于实际业务的二维码工具,至少需要覆盖下面几个环节:

  1. 根据场景组织二维码内容。
  2. 设置纠错等级、颜色和安全留白。
  3. 支持PNG和SVG导出。
  4. 对生成结果进行反向解码检查。
  5. 批量任务需要识别错误和重复数据。
  6. 导出文件需要附带数据清单。
  7. SKU标签需要按照毫米尺寸和DPI生成。
  8. 正式使用前仍需完成真实设备和印刷测试。
相关推荐
计算机魔术师17 分钟前
从卖铲人到圈地:英伟达129亿拿下Hugging Face
前端
攻城狮-申21 分钟前
git本地分支对齐远程分支
前端·git
IT_陈寒28 分钟前
Vite热更新突然失效?可能是这个配置在捣鬼
前端·人工智能·后端
一只小阿乐36 分钟前
java 快速上手开发 2
java·开发语言·前端
nicole bai1 小时前
dialog封装
前端·javascript·vue.js
高彬1 小时前
SAP ECC6.0 对接 C# web Services服务
开发语言·前端·c#·sap
小江的记录本1 小时前
【CSS】CSS 核心:盒模型、BFC/IFC、Flex/Grid 布局、响应式布局、移动端适配(附《思维导图》)
前端·css·面试·前端框架·tensorflow·html5·xss
Coodor1 小时前
BS架构下在前端读写超高频UHF标签
前端·超高频·rfid读写器web插件·uhf标签
一晌小贪欢1 小时前
python-第20天:关键字参数与不定长参数
java·开发语言·前端·python·数据可视化·函数参数·python办公