二维码生成看起来只是调用一个库,但如果要用于商品标签、说明书、展会资料或批量生产,还需要解决几个工程问题:
- 怎样导出清晰的PNG和SVG?
- 加入Logo后如何保持可扫描?
- 怎样在下载前自动验证二维码?
- 如何批量处理CSV数据?
- 毫米尺寸的标签如何转换成300 DPI图片?
- 怎样避免重复内容和错误网址进入导出文件?
本文使用 qrcode、jsQR 和 JSZip,实现一套完全运行在浏览器中的二维码处理流程。
一、安装依赖
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后建议:
- 将纠错等级调整为H。
- Logo宽度控制在二维码宽度的15%到20%左右。
- 不要遮挡三个定位图案。
- Logo周围保留浅色背景。
- 绘制完成后重新运行扫码检测。
即使检测通过,也不建议把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%缩放,否则浏览器可能改变实际标签尺寸。
十、实际开发中容易忽略的问题
二维码内容过长
完整网址附带大量查询参数时,二维码会变得非常密集。可以缩短不必要的参数,但不要随意使用无法长期维护的临时短链接。
前景色和背景色对比不足
浅蓝色配白色、灰色配透明背景,可能在屏幕上看起来正常,印刷后却难以识别。
留白被设计稿裁掉
二维码四周需要保留安全区域。设计师在排版时如果把留白裁掉,扫码成功率会明显下降。
只检查第一张二维码
批量任务中,每一条数据的长度都可能不同。第一张可以识别,不代表后面的二维码都能识别。
二维码指向临时页面
二维码印刷以后很难修改,因此目标网址应尽量使用可长期维护的域名和路径。
总结
一个可用于实际业务的二维码工具,至少需要覆盖下面几个环节:
- 根据场景组织二维码内容。
- 设置纠错等级、颜色和安全留白。
- 支持PNG和SVG导出。
- 对生成结果进行反向解码检查。
- 批量任务需要识别错误和重复数据。
- 导出文件需要附带数据清单。
- SKU标签需要按照毫米尺寸和DPI生成。
- 正式使用前仍需完成真实设备和印刷测试。