1.背景
接到UI需求,需要将动态生成的方形二维码修改成带圆角的二维码。效果如下:
2.分析
首先,动态二维码是从服务端下发的原始码信息传,再由前端的二维码库计算后用canvas绘制而成。而服务下发的原始信息传的长度是不确定的,所以计算出的三个定位点的尺寸(注意:按像素计算)是不确定的,所以直接通过css将canvas画布切掉可能造成码取信息被切割,造成二维码无法被扫码机具识别。
所以想要完成以上需求就必须从二维码生成库进行修改。想要修改二维码生成库,首先需要了解二维码生成原理,参见:https://coolshell.cn/articles/10590.html 。
阅读完上面二维码生成的原理,我们可以get到几个。
- 需要修改的三个锚点点叫做Position Detection,作用是标定二维码位置的。
- 无论二维码是什么版本的(version),这三个锚点为逻辑尺寸是固定的。
这三个锚点的位置也是固定的。
所以逻辑上我们可以识别这三个锚点,并对其进行特殊绘制,以达到需求的效果。
3.上代码
首先,查看我们自己的二维码生成库基于qrcode.js。QRCodeModel就是从qrcode.js库中导出的对象。
使用如下:
const correctLevel = 1;
const qrCode = new QRCodeModel(-1, correctLevel);
// 添加数据
qrCode.addData(url);
qrCode.make();
// 每个方向的二维码数量
const nCount = qrCode.getModuleCount();
// 计算每个二维码方块的大小
let tileW = options.width / moduleCount
let tileH = options.height / moduleCount
// ctx为canvas的context
ctx.fillStyle = options.background
ctx.fillRect(0,0, options.width, options.height);
ctx.fillStyle = options.foreground
for (let row = 0; row < moduleCount; row++) {
for (let col = 0; col < moduleCount; col++) {
// 是否为Position Detection
const isBlkPosCtr = (col < 8 && (row < 8 || row >= moduleCount - 8)) || (col >= moduleCount - 8 && row < 8);
// 是否是Timing Patterns,也是用于协助定位扫描的
// const isTiming = (row == 6 && col >= 8 && col <= moduleCount - 8) || (col == 6 && row >= 8 && row <= moduleCount - 8);
// ctx.setFillStyle(style)
// 信息区域绘制
if(qrcode.isDark(row, col) && !isBlkPosCtr) {
var w = (Math.ceil((col + 1) * tileW) - Math.floor(col * tileW))
var h = (Math.ceil((row + 1) * tileW) - Math.floor(row * tileW))
ctx.fillRect(Math.round(col * tileW) + options.x, Math.round(row * tileH) + options.y, w, h)
}
}
}
- 修改点为判断绘制的区域为非锚点区域(isBlkPosCtr为true)。这样绘制出的二维码只有信息区域而没有锚点,效果如下:
2.接下来我们绘制三个锚点, 每一个锚点都是如图的有一个圆角的三个同心正方形嵌套做得,不同点是圆角位置不同。实现代码如下:
function drawBP1Radius({options, ctx, tileW, tileH, moduleCount}) {
// 外层
ctx.beginPath()
ctx.fillStyle = options.foreground;
ctx.moveTo(0 + options.x, Math.round(3 * tileH) + options.y);
ctx.quadraticCurveTo(
options.x,
options.y,
Math.round(3 * tileW) + options.x,
0 + options.y
);
ctx.lineTo(Math.round(7 * tileW) + options.x, 0 + options.y);
ctx.lineTo(Math.round(7 * tileW) + options.x, Math.round(7 * tileH) + options.y);
ctx.lineTo(0 + options.x, Math.round(7 * tileH) + options.y);
ctx.lineTo(0 + options.x, Math.round(3 * tileH) + options.y);
ctx.fill();
ctx.moveTo(Math.round((moduleCount - 3) * tileW) + options.x, 0 + options.y);
ctx.quadraticCurveTo(
Math.round(moduleCount * tileW) + options.x,
0 + options.y,
Math.round(moduleCount * tileW) + options.x,
Math.round(3 * tileH) + options.y
);
ctx.lineTo(Math.round(moduleCount * tileW) + options.x, Math.round(7 * tileH) + options.y);
ctx.lineTo(Math.round((moduleCount - 7) * tileW) + options.x, Math.round(7 * tileH) + options.y);
ctx.lineTo(Math.round((moduleCount - 7) * tileW) + options.x, 0 + options.y);
ctx.lineTo(Math.round((moduleCount - 3) * tileW) + options.x, 0 + options.y);
ctx.fill();
ctx.moveTo(0 + options.x, Math.round((moduleCount - 3) * tileH) + options.y);
ctx.quadraticCurveTo(
0 + options.x,
Math.round(moduleCount * tileH) + options.y,
Math.round(3 * tileW) + options.x,
Math.round(moduleCount * tileH) + options.y
);
ctx.lineTo(Math.round(7 * tileW) + options.x, Math.round(moduleCount * tileH) + options.y);
ctx.lineTo(Math.round(7 * tileW) + options.x, Math.round((moduleCount - 7) * tileH) + options.y);
ctx.lineTo(0 + options.x, Math.round((moduleCount - 7) * tileH) + options.y);
ctx.lineTo(0 + options.x, Math.round((moduleCount - 3) * tileH) + options.y);
ctx.fill();
// 中层
ctx.beginPath()
ctx.fillStyle = options.background;
ctx.moveTo(Math.round(1 * tileW) + options.x, Math.round(3 * tileH) + options.y);
ctx.quadraticCurveTo(
Math.round(1 * tileW) + options.x,
Math.round(1 * tileH) + options.y,
Math.round(3 * tileW) + options.x,
Math.round(1 * tileH) + options.y
);
ctx.lineTo(Math.round(6 * tileW) + options.x, Math.round(1 * tileH) + options.y);
ctx.lineTo(Math.round(6 * tileW) + options.x, Math.round(6 * tileH) + options.y);
ctx.lineTo(Math.round(1 * tileW) + options.x, Math.round(6 * tileH) + options.y);
ctx.lineTo(Math.round(1 * tileW) + options.x, Math.round(3 * tileH) + options.y);
ctx.fill();
ctx.moveTo(Math.round((moduleCount - 3) * tileW) + options.x, Math.round(1 * tileH) + options.y);
ctx.quadraticCurveTo(
Math.round((moduleCount -1) * tileW) + options.x,
Math.round(1 * tileH) + options.y,
Math.round((moduleCount - 1) * tileW) + options.x,
Math.round(3 * tileH) + options.y
);
ctx.lineTo(Math.round((moduleCount - 1) * tileW) + options.x, Math.round(6 * tileH) + options.y);
ctx.lineTo(Math.round((moduleCount - 6) * tileW) + options.x, Math.round(6 * tileH) + options.y);
ctx.lineTo(Math.round((moduleCount - 6) * tileW) + options.x, Math.round(1 * tileH) + options.y);
ctx.lineTo(Math.round((moduleCount - 3) * tileW) + options.x, Math.round(1 * tileH) + options.y);
ctx.fill();
ctx.moveTo(Math.round(1 * tileW) + options.x, Math.round((moduleCount - 3) * tileH) + options.y);
ctx.quadraticCurveTo(
Math.round(1 * tileW) + options.x,
Math.round((moduleCount-1) * tileH) + options.y,
Math.round(3 * tileW) + options.x,
Math.round((moduleCount-1) * tileH) + options.y
);
ctx.lineTo(Math.round(6 * tileW) + options.x, Math.round((moduleCount-1) * tileH) + options.y);
ctx.lineTo(Math.round(6 * tileW) + options.x, Math.round((moduleCount - 6) * tileH) + options.y);
ctx.lineTo(Math.round(1 * tileW) + options.x, Math.round((moduleCount - 6) * tileH) + options.y);
ctx.lineTo(Math.round(1 * tileW) + options.x, Math.round((moduleCount - 3) * tileH) + options.y);
ctx.fill();
// 内层
ctx.beginPath()
ctx.fillStyle = options.foreground;
ctx.moveTo(Math.round(2 * tileW) + options.x, Math.round(3 * tileH) + options.y);
ctx.quadraticCurveTo(
Math.round(2 * tileW) + options.x,
Math.round(2 * tileH) + options.y,
Math.round(3 * tileW) + options.x,
Math.round(2 * tileH) + options.y
);
ctx.lineTo(Math.round(5 * tileW) + options.x, Math.round(2 * tileH) + options.y);
ctx.lineTo(Math.round(5 * tileW) + options.x, Math.round(5 * tileH) + options.y);
ctx.lineTo(Math.round(2 * tileW) + options.x, Math.round(5 * tileH) + options.y);
ctx.lineTo(Math.round(2 * tileW) + options.x, Math.round(3 * tileH) + options.y);
ctx.fill();
ctx.moveTo(Math.round((moduleCount - 3) * tileW) + options.x, Math.round(2 * tileH) + options.y);
ctx.quadraticCurveTo(
Math.round((moduleCount -2) * tileW) + options.x,
Math.round(2 * tileH) + options.y,
Math.round((moduleCount - 2) * tileW) + options.x,
Math.round(3 * tileH) + options.y
);
ctx.lineTo(Math.round((moduleCount - 2) * tileW) + options.x, Math.round(5 * tileH) + options.y);
ctx.lineTo(Math.round((moduleCount - 5) * tileW) + options.x, Math.round(5 * tileH) + options.y);
ctx.lineTo(Math.round((moduleCount - 5) * tileW) + options.x, Math.round(2 * tileH) + options.y);
ctx.lineTo(Math.round((moduleCount - 3) * tileW) + options.x, Math.round(2 * tileH) + options.y);
ctx.fill();
ctx.moveTo(Math.round(2 * tileW) + options.x, Math.round((moduleCount - 3) * tileH) + options.y);
ctx.quadraticCurveTo(
Math.round(2* tileW) + options.x,
Math.round((moduleCount-2) * tileH) + options.y,
Math.round(3 * tileW) + options.x,
Math.round((moduleCount-2) * tileH) + options.y
);
ctx.lineTo(Math.round(5 * tileW) + options.x, Math.round((moduleCount-2) * tileH) + options.y);
ctx.lineTo(Math.round(5 * tileW) + options.x, Math.round((moduleCount - 5) * tileH) + options.y);
ctx.lineTo(Math.round(2 * tileW) + options.x, Math.round((moduleCount - 5) * tileH) + options.y);
ctx.lineTo(Math.round(2 * tileW) + options.x, Math.round((moduleCount - 3) * tileH) + options.y);
ctx.fill();
}
完工。
其他酷炫的二维码生成库:wx-qr
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。