背景
最近接触到的需求,前端生成一个带企业logo的二维码,并支持点击下载它。
实现
在前面的文章有讲到如何用 canvas 画二维码,在此基础上再画一个公司logo,并提供下载的方法供调用,再封装成 npm 插件
模块名称: qrcode-with-logos
github地址:https://github.com/zxpsuper/qrcode-with-logos
npm地址:https://www.npmjs.com/package/qrcode-with-logos
核心代码
将整个封装成一个 QrCodeWithLogo
类,并提供三个方法:
interface IQrCodeWithLogo {
toCanvas(): Promise;
toImage(): Promise;
downloadImage(name: string): void;
}
class QrCodeWithLogo implements IQrCodeWithLogo {
option: BaseOptions;
constructor(option: BaseOptions) {
this.option = option;
return this;
}
toCanvas = () => {
return toCanvas.call(this, this.option);
};
toImage = () => {
return toImage.call(this, this.option);
};
downloadImage = (name: string) => {
saveImage(this.option.image, name);
};
}
1. toCanvas()
此方法用到了库qrcode
的toCanvas
方法
export const toCanvas = (options: BaseOptions) => {
return renderQrCode(options)
.then(() => options)
.then(drawLogo);
};
这里先用qrcode
库画出二维码的canvas
import QRCode = require("qrcode");
const toCanvas = promisify(QRCode.toCanvas);
export const renderQrCode = ({
canvas,
content,
width = 0,
nodeQrCodeOptions = {}
}: BaseOptions) => {
// 容错率,默认对内容少的二维码采用高容错率,内容多的二维码采用低容错率
// according to the content length to choose different errorCorrectionLevel
nodeQrCodeOptions.errorCorrectionLevel =
nodeQrCodeOptions.errorCorrectionLevel || getErrorCorrectionLevel(content);
return getOriginWidth(content, nodeQrCodeOptions).then((_width: number) => {
// 得到原始比例后还原至设定值,再放大4倍以获取高清图
// Restore to the set value according to the original ratio, and then zoom in 4 times to get the HD image.
nodeQrCodeOptions.scale = width === 0 ? undefined : (width / _width) * 4;
// @ts-ignore
return toCanvas(canvas, content, nodeQrCodeOptions);
});
};
promisify()是封装的一个方法,用于减少return promise时的代码,方便书写
export const promisify = (f: Function): Function => {
return function() {
const args = Array.prototype.slice.call(arguments);
return new Promise(function(resolve, reject) {
args.push(function(err: object, result: object) {
if (err) reject(err);
else resolve(result);
});
f.apply(null, args);
});
};
};
画出canvas,紧接着判断是否有logo, 如果有就画logo,这里有两种模式:
- 一种是直接画图
ctx.drawImage(image, logoXY, logoXY, logoWidth, logoWidth);
,可拓展性不强。 - 一种是canvas叠加,使用
ctx.createPattern(canvasImage, "no-repeat");
可以实现更多复杂的效果
export const drawLogo = ({ canvas, content, logo }: BaseOptions) => {
if (!logo) {
return;
}
// @ts-ignore
const canvasWidth = canvas.width;
const {
logoSize = 0.15,
borderColor = "#ffffff",
bgColor = borderColor || "#ffffff",
borderSize = 0.05,
crossOrigin,
borderRadius = 8,
logoRadius = 0
} = logo;
let logoSrc = typeof logo === "string" ? logo : logo.src;
let logoWidth = canvasWidth * logoSize;
let logoXY = (canvasWidth * (1 - logoSize)) / 2;
let logoBgWidth = canvasWidth * (logoSize + borderSize);
let logoBgXY = (canvasWidth * (1 - logoSize - borderSize)) / 2;
// @ts-ignore
const ctx = canvas.getContext("2d");
// logo 底色, draw logo background color
canvasRoundRect(ctx)(
logoBgXY,
logoBgXY,
logoBgWidth,
logoBgWidth,
borderRadius
);
ctx.fillStyle = bgColor;
ctx.fill();
// logo
const image = new Image();
if (crossOrigin || logoRadius) {
image.setAttribute("crossOrigin", crossOrigin || "anonymous");
}
image.src = logoSrc;
// 使用image绘制可以避免某些跨域情况
// Use image drawing to avoid some cross-domain situations
const drawLogoWithImage = (image: any) => {
ctx.drawImage(image, logoXY, logoXY, logoWidth, logoWidth);
};
// 使用canvas绘制以获得更多的功能
// Use canvas to draw more features, such as borderRadius
const drawLogoWithCanvas = (image: any) => {
const canvasImage = document.createElement("canvas");
canvasImage.width = logoXY + logoWidth;
canvasImage.height = logoXY + logoWidth;
canvasImage
.getContext("2d")
.drawImage(image, logoXY, logoXY, logoWidth, logoWidth);
canvasRoundRect(ctx)(logoXY, logoXY, logoWidth, logoWidth, logoRadius);
ctx.fillStyle = ctx.createPattern(canvasImage, "no-repeat");
ctx.fill();
};
// 将 logo绘制到 canvas上
// Draw the logo on the canvas
return new Promise((resolve, reject) => {
image.onload = () => {
logoRadius ? drawLogoWithCanvas(image) : drawLogoWithImage(image);
resolve();
};
});
};
2. toImage()
此方法利用之前的toCanvas()
方法,生成canvas后拿到 canvas.toDataURL()
的值,赋给的
src
即可。这里,我们加入了 download
downloadName
属性用于下载,因此在 toImage()
方法中判断,代码如下:
export const toImage = (options: BaseOptions) => {
const canvas = document.createElement("canvas");
console.log("options", options);
options.canvas = canvas;
if (options.logo) {
if (isString(options.logo)) {
// @ts-ignore
options.logo = { src: options.logo };
}
// @ts-ignore
options.logo.crossOrigin = "Anonymous";
}
// @ts-ignore
return toCanvas(options).then(() => {
const { image = new Image(), downloadName = "qr-code" } = options;
let { download } = options;
// @ts-ignore
image.src = canvas.toDataURL();
if (download !== true && !isFunction(download)) {
return;
}
download = download === true ? (start: Function) => start() : download;
const startDownload: Function = () => {
saveImage(image, downloadName);
};
download && download(startDownload);
return new Promise((resolve, reject) => {
resolve();
});
});
};
export const saveImage = (image: Element, name: string) => {
// @ts-ignore
const dataURL = image.src;
const link = document.createElement("a");
link.download = name;
link.href = dataURL;
link.dispatchEvent(new MouseEvent("click"));
};
3. downloadImage(name)
提供一个主动调用下载图片的方法,传入文件名name, 其中用到 saveImage()
方法,这个在 toImage()
中也有用到。
下载文件的流程是:生成一个标签, 设置
href
值为 image
的 src
值,download
属性赋值文件名,然后给 主动一次点击事件即可。
downloadImage = (name: string) => {
saveImage(this.option.image, name);
};
npm 发布
初次尝试 typescript
, 用的不够优美但无妨使用。 npm run build
构建出支持 umd 的文件,然后 npm login
, npm publish
即可。webpack 配置可查看 github 代码。
插件使用
下面是详细代码
npm 模块导入:
import QrCodeWithLogo from "qrcode-with-logos";
let qrcode = new QrCodeWithLogo({
canvas: document.getElementById("canvas"),
content: "https://github.com/zxpsuper",
width: 380,
// download: true,
image: document.getElementById("image"),
logo: {
src: "https://avatars1.githubusercontent.com/u/28730619?s=460&v=4"
}
});
qrcode.toCanvas().then(() => {
qrcode.toImage().then(() => {
setTimeout(() => {
qrcode.downloadImage("hello world");
}, 2000);
});
});
当然你也可以引入使用
That is all.