一、二维码基础概念

1.1 二维码的定义

二维码是在一维条码基础上发展的矩阵式二维条码,通过矩形区域内黑白像素矩阵编码信息。与一维条码相比,它无需线性扫描,手机等设备从任意角度拍摄即可快速解码,能存储更丰富的信息,包括文本、链接、联系人信息等。

1.2 主流二维码格式

格式 特点 适用场景
QR Code 正方形,有3个定位角标,支持多种数据,容错率高(15%-30%) 支付、扫码登录、信息查询等
Data Matrix 尺寸小、密度高 电子元件、药品包装等小型产品
PDF417 长方形,存储容量大 身份证背面、物流单等
Aztec Code 无定位角标,识别速度快 航空登机牌、门票等

二、二维码存储容量计算

2.1 影响存储容量的核心因素

二维码存储容量由版本(Version)、纠错级别(Error Correction Level)和编码模式(Encoding Mode)共同决定。

  • 版本:从1到40,决定矩阵大小,公式为模块数=21+4×(版本-1),如版本1为21×21模块,版本40为177×177模块。
  • 纠错级别:分为L(7%)、M(15%)、Q(25%)、H(30%),级别越高,容量越小但抗损坏能力越强。
  • 编码模式:不同数据类型采用不同编码模式,效率不同。

2.2 不同编码模式的容量差异

编码模式 处理内容 最大容量(版本40-L级)
数字(Numeric) 0-9 7,089个数字
字母数字(Alphanumeric) A-Z、0-9、部分符号 4,296个字符
字节(Byte) ASCII、UTF-8 2,953字节
汉字(Kanji) 中文、日文等双字节字符 1,817个字符

2.3 容量计算示例

以版本3、纠错级别M的QR码在字母数字模式下的最大容量计算为例:

  1. 查标准表得版本3-M级的数据码字为44字节。
  2. 字母数字模式下,2字符对应11bit(1.375字节/字符)。
  3. 计算:44÷1.375=32字符。
  4. 验证:44×8=352bit,减去模式标识(4bit)、字符计数(9bit)、终止符(4bit),剩余335bit,335÷11×2≈60.9,取整为32个字符。

三、二维码生成的核心步骤

3.1 数据编码

根据输入数据类型选择编码模式,将数据转化为二进制流,并添加模式标识和字符计数。

// 简单模拟数字模式编码过程(实际由库实现)
function encodeNumeric(data) {
  let binary = '';
  // 每3个数字转为10位二进制
  for (let i = 0; i < data.length; i += 3) {
    const chunk = data.substr(i, 3);
    const num = parseInt(chunk, 10);
    // 转为10位二进制,不足补前导0
    binary += num.toString(2).padStart(10, '0');
  }
  // 添加模式标识(数字模式为0001)和字符计数
  return '0001' + data.length.toString(2).padStart(10, '0') + binary;
}
// 示例:编码"123"
console.log(encodeNumeric("123")); // 输出包含模式标识、计数和编码后的二进制

3.2 纠错码生成(基于里德-所罗门算法)

将编码后的二进制流拆分为数据块,通过里德-所罗门算法生成纠错码块,再混合形成完整数据序列。

3.3 矩阵布局与填充

初始化矩阵,绘制定位图案、对齐图案等固定图案,将完整数据序列按“之”字形规则填充,应用掩码优化识别率。

四、qrcode.js前端实现详解

4.1 qrcode.js简介

qrcode.js是轻量、无依赖的前端二维码生成库,支持Canvas和Image渲染,API简洁,适合生成网址、文本等二维码。

4.2 基础使用

4.2.1 引入库

<!-- CDN引入 -->
<script src="https://cdn.jsdelivr.net/npm/qrcode@1.5.1/build/qrcode.min.js"></script>

4.2.2 生成基本二维码

<!-- 容器 -->
<canvas id="qrcodeCanvas"></canvas>
<img id="qrcodeImg" />

<script>
  const content = "https://example.com"; // 二维码内容

  // 生成到Canvas
  QRCode.toCanvas(document.getElementById("qrcodeCanvas"), content, (error) => {
    if (error) console.error(error);
    else console.log("Canvas二维码生成成功");
  });

  // 生成到Image(返回DataURL)
  QRCode.toDataURL(content, (error, url) => {
    if (error) console.error(error);
    else document.getElementById("qrcodeImg").src = url;
  });
</script>

4.3 高级配置

<canvas id="customQrcode"></canvas>

<script>
  const options = {
    width: 300, // 宽度
    height: 300, // 高度
    color: {
      dark: "#3366ff", // 深色模块颜色
      light: "#f0f8ff" // 浅色背景颜色
    },
    errorCorrectionLevel: "H", // 高容错级别
    margin: 2 // 边缘留白
  };

  QRCode.toCanvas(
    document.getElementById("customQrcode"),
    "自定义样式的二维码",
    options,
    (error) => {
      if (error) console.error(error);
      else console.log("自定义二维码生成成功");
    }
  );
</script>

4.4 动态生成二维码

<input type="text" id="inputContent" placeholder="输入内容生成二维码">
<canvas id="dynamicCanvas"></canvas>

<script>
  const input = document.getElementById("inputContent");
  const canvas = document.getElementById("dynamicCanvas");

  // 输入框变化时重新生成
  input.addEventListener("input", (e) => {
    const content = e.target.value.trim() || "请输入内容";
    QRCode.toCanvas(canvas, content, (error) => {
      if (error) console.error(error);
    });
  });
</script>

4.5 为二维码添加Logo

<canvas id="logoQrcode"></canvas>

<script>
  const canvas = document.getElementById("logoQrcode");
  const content = "带Logo的二维码";
  const logoUrl = "logo.png"; // Logo图片地址

  // 生成二维码后添加Logo
  QRCode.toCanvas(canvas, content, { errorCorrectionLevel: "H" }, (error) => {
    if (error) return console.error(error);

    const ctx = canvas.getContext("2d");
    const logo = new Image();
    logo.crossOrigin = "anonymous"; // 解决跨域
    logo.onload = () => {
      const logoSize = canvas.width / 5; // Logo大小为二维码的1/5
      const x = (canvas.width - logoSize) / 2; // 水平居中
      const y = (canvas.height - logoSize) / 2; // 垂直居中

      // 绘制Logo背景(白色圆形边框)
      ctx.fillStyle = "#ffffff";
      ctx.beginPath();
      ctx.arc(x + logoSize/2, y + logoSize/2, logoSize/2 + 5, 0, 2*Math.PI);
      ctx.fill();

      // 绘制Logo
      ctx.drawImage(logo, x, y, logoSize, logoSize);
    };
    logo.src = logoUrl;
  });
</script>

4.6 qrcode.js中数据填充和补位过程

4.6.1 补位过程

补位用于解决数据长度不足的问题,使用固定序列11101100(0xEC)和00010001(0x11)循环填充。

// 模拟补位过程(简化版)
function padData(dataBits, totalBits) {
  const padSequence = [0xEC, 0x11]; // 补位序列
  let padBits = '';
  let remaining = totalBits - dataBits;
  let index = 0;

  while (remaining > 0) {
    // 取补位序列的当前字节转为8位二进制
    const byteBits = padSequence[index].toString(2).padStart(8, '0');
    // 取需要的位数
    const take = Math.min(remaining, 8);
    padBits += byteBits.substr(0, take);
    remaining -= take;
    index = (index + 1) % 2; // 循环切换序列
  }

  return dataBits + padBits;
}
// 示例:假设数据位420,需要补到448位
const dataBits = '1010...'; // 省略部分二进制
const paddedBits = padData(dataBits, 448);

4.6.2 数据填充过程

将补位后的二进制流转换为数据码字(8位字节),并分割为数据块。

// 模拟数据填充为数据码字并分块(简化版)
function packData(paddedBits, blockCount) {
  // 转换为数据码字(每8位一个字节)
  const codewords = [];
  for (let i = 0; i < paddedBits.length; i += 8) {
    const byteStr = paddedBits.substr(i, 8);
    codewords.push(parseInt(byteStr, 2));
  }

  // 分割为数据块
  const blockSize = Math.floor(codewords.length / blockCount);
  const blocks = [];
  let offset = 0;

  for (let i = 0; i < blockCount; i++) {
    // 最后一个块可能多1个字节
    const size = i === blockCount - 1 ? codewords.length - offset : blockSize;
    blocks.push(codewords.slice(offset, offset + size));
    offset += size;
  }

  return blocks;
}
// 示例:将补位后的二进制转为数据块(假设分为2块)
const blocks = packData(paddedBits, 2);

五、总结

本指南详细介绍了二维码的基础概念、存储容量计算、生成核心步骤以及qrcode.js的前端实现,包括基础使用、高级配置、动态生成、添加Logo等操作,并解析了qrcode.js中数据填充和补位的过程。通过学习这些知识,初学者可以全面了解二维码技术,并能在前端项目中灵活应用qrcode.js生成符合需求的二维码。