Last active
October 24, 2022 02:18
-
-
Save watert/c7fe42a54a7130a15a2a2f3e741a6b0f to your computer and use it in GitHub Desktop.
适用于小程序的通用化的 QRCode 生成器
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
create-qrcode.js | |
================ | |
基于 qr.js 进行包装, 适用于小程序的通用化的 QRCode JS 生成器 | |
qr.js 是已经比较成熟的二维码生成相关的 JS 移植,然而其内部的渲染机制需要浏览器相关特性的支持,使得在小程序端无法直接使用。 | |
小程序中也无法使用 SVG 直接渲染,而如果使用 DOM 结构渲染或者 Canvas 渲染的话,需要的开发和接入成本也会上升。 | |
因此这里使用的方案是: SVG 字符串生成后,转化为 dataURL 字符串,可以直接作为小程序中的 image 标签的 src 属性直接使用。 | |
因为是 SVG 矢量图, 因此可以渲染为任意尺寸。 | |
### INSTALLATION 安装: | |
在项目中通过 npm 等包管理器安装 qr.js 即可 | |
```bash | |
npm i qr.js -S | |
``` | |
### USAGE 调用方法: | |
```jsx | |
import createQRCode from './create-qrcode'; | |
<image src={createQRCode({ value: '二维码内容', fgColor: 'black' })} /> | |
``` | |
参数: | |
* @param {obj} options | |
* @param {string} options.level - 容错级别 L|M|Q|H, 默认为 L | |
* @param {string} options.bgColor - 背景色, 默认为 null, 即不使用背景色 | |
* @param {string} options.fgColor - 前景色, 即二维码的颜色, 默认为黑色 | |
* @param {boolean} opitons.dataURL - 是否使用 dataURL 生成, 默认为 true, 否则会返回原始 SVG 字符串 | |
* @returns | |
* */ | |
import QRCodeImpl from 'qr.js/lib/QRCode'; | |
import ErrorCorrectLevel from 'qr.js/lib/ErrorCorrectLevel'; | |
function convertStr(str) { | |
let out = ''; | |
for (let i = 0; i < str.length; i++) { | |
let charcode = str.charCodeAt(i); | |
if (charcode < 0x0080) { | |
out += String.fromCharCode(charcode); | |
} else if (charcode < 0x0800) { | |
out += String.fromCharCode(0xc0 | (charcode >> 6)); | |
out += String.fromCharCode(0x80 | (charcode & 0x3f)); | |
} else if (charcode < 0xd800 || charcode >= 0xe000) { | |
out += String.fromCharCode(0xe0 | (charcode >> 12)); | |
out += String.fromCharCode(0x80 | ((charcode >> 6) & 0x3f)); | |
out += String.fromCharCode(0x80 | (charcode & 0x3f)); | |
} else { | |
// This is a surrogate pair, so we'll reconsitute the pieces and work | |
// from that | |
i += 1; | |
charcode = 0x10000 + (((charcode & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff)); | |
out += String.fromCharCode(0xf0 | (charcode >> 18)); | |
out += String.fromCharCode(0x80 | ((charcode >> 12) & 0x3f)); | |
out += String.fromCharCode(0x80 | ((charcode >> 6) & 0x3f)); | |
out += String.fromCharCode(0x80 | (charcode & 0x3f)); | |
} | |
} | |
return out; | |
} | |
function generatePath(modules, margin) { | |
const ops = []; | |
modules.forEach((row, y) => { | |
let start = null; | |
row.forEach((cell, x) => { | |
if (!cell && start !== null) { | |
// M0 0h7v1H0z injects the space with the move and drops the comma, | |
// saving a char per operation | |
ops.push( | |
`M${start + margin} ${y + margin}h${x - start}v1H${start + margin}z`, | |
); | |
start = null; | |
return; | |
} | |
// end of row, clean up or skip | |
if (x === row.length - 1) { | |
if (!cell) { | |
// We would have closed the op above already so this can only mean | |
// 2+ light modules in a row. | |
return; | |
} | |
if (start === null) { | |
// Just a single dark module. | |
ops.push(`M${x + margin},${y + margin} h1v1H${x + margin}z`); | |
} else { | |
// Otherwise finish the current line. | |
ops.push( | |
`M${start + margin},${y + margin} h${(x + 1) - start}v1H${start | |
+ margin}z`, | |
); | |
} | |
return; | |
} | |
if (cell && start === null) { | |
start = x; | |
} | |
}); | |
}); | |
return ops.join(''); | |
} | |
/** | |
* | |
* @param {obj} options | |
* @param {string} options.level - 容错级别 L|M|Q|H, 默认为 L | |
* @param {string} options.bgColor - 背景色, 默认为 null, 即不使用背景色 | |
* @param {string} options.fgColor - 前景色, 即二维码的颜色, 默认为黑色 | |
* @param {boolean} opitons.dataURL - 是否使用 dataURL 生成, 默认为 true, 否则会返回原始 SVG 字符串 | |
* @returns | |
*/ | |
export default function createSvg(options) { | |
if (typeof options === 'string') options = { value: options }; | |
options = { | |
level: 'L', // L | M | Q | H | |
bgColor: null, | |
fgColor: '#000000', | |
includeMargin: false, | |
margin: 0, | |
dataURL: true, | |
...options, | |
}; | |
const { bgColor, fgColor, value, level, dataURL, margin } = options; | |
const qrcode = new QRCodeImpl(-1, ErrorCorrectLevel[level]); | |
qrcode.addData(convertStr(value)); | |
qrcode.make(); | |
const cells = qrcode.modules; | |
const numCells = cells.length + (margin * 2); | |
const path = generatePath(cells, margin); | |
const viewBox = `0 0 ${numCells} ${numCells}`; | |
const bgPath = bgColor ? `<path fill="${bgColor}" d="M0,0 h${numCells} v${numCells} H0z" />` : ''; | |
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${viewBox}" | |
preserveAspectRatio="xMidYMid meet" style="vertical-align: middle;" | |
> | |
<g> | |
${bgPath} | |
<path fill="${fgColor}" d="${path}"></path> | |
</g> | |
</svg>`; | |
if (dataURL) return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`; | |
return svg; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment