专栏首页小皮咖Canvas 进阶(二)写一个生成带logo的二维码npm插件

Canvas 进阶(二)写一个生成带logo的二维码npm插件

背景

最近接触到的需求,前端生成一个带企业logo的二维码,并支持点击下载它。

实现

在前面的文章有讲到如何用 canvas 画二维码,在此基础上再画一个公司logo,并提供下载的方法供调用,再封装成 npm 插件

模块名称: qrcode-with-logos

github地址:github.com/zxpsuper/qr…

npm地址:www.npmjs.com/package/qrc…

核心代码

将整个封装成一个 QrCodeWithLogo类,并提供三个方法:

interface IQrCodeWithLogo {
  toCanvas(): Promise<any>;
  toImage(): Promise<any>;
  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()

此方法用到了库qrcodetoCanvas方法

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() 的值,赋给<img>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() 中也有用到。

下载文件的流程是:生成一个<a>标签, 设置 href值为 imagesrc 值,download 属性赋值文件名,然后给 <a> 主动一次点击事件即可。

downloadImage = (name: string) => {
    saveImage(this.option.image, name);
};
复制代码

npm 发布

初次尝试 typescript, 用的不够优美但无妨使用。 npm run build 构建出支持 umd 的文件,然后 npm login, npm publish 即可。webpack 配置可查看 github 代码。

插件使用

下面是详细代码

<canvas id="canvas"></canvas> <img src="" alt="" id="image" />
<img id="image" alt="">
复制代码

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);
  });
});
复制代码

当然你也可以<script>引入使用

<script src="https://zxpsuper.github.io/qrcode-with-logos/dist/QRcode-with-logo.js"></script>
<script>
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);
  });
});
</script>

复制代码

That is all.

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 开发一个简单的脚手架工具

    像我们熟悉的 vue-cli,create-react-app 等脚手架,只需要输入简单的命令 vue init webpack project,即可快速帮我们...

    小皮咖
  • Canvas 进阶(六)实现图片压缩功能

    因此我们设计一个 imageCompress 类,传入一个 option, 其参数有:

    小皮咖
  • Canvas 进阶(一)二维码的生成与扫码识别

    因此,打算写一系列关于 canvas 的文章,探索学习提升自己的同时顺便分享给大家。

    小皮咖
  • useRef 进阶

    一直以来使用useRef的场景比较常见和基础,大多是为了操作已经mount的dom节点,例如设置焦点之类的,如官方例子所示:

    CIKEY
  • 如何优雅地测量一只猫的体积

    大数据文摘
  • Access数据库表字段类型

    大家好,上节简单演示在Access数据库中创建对应的表的步骤。本节简单汇总下字段的数据类型,属性在下节介绍。

    无言之月
  • 如何优雅地测量一只猫的体积?

    把猫装进已知体积为V_box的盒子,在盒子内均匀取N个随机点,其中M个在猫体内,猫体积近似为V_box*M/N。推理及讨论见后面的supplemental ma...

    华章科技
  • OpenGL ES 2.0 (iOS)[01]: 一步从一个小三角开始

    1). 三个什么端点(屏幕坐标点)? 要回答这个问题要先了解 OpenGL ES 的坐标系在屏幕上是怎样分布的:

    半纸渊
  • 锦囊篇|一文摸懂OkHttp

    最近都在学校上课,三天满课,剩下还要课程复习维持绩点,基本上维持周更也已经比较吃力了,不过还是会继续坚持,之后的推文基本上会在周天推,嘻嘻。

    ClericYi
  • Scala和Kotlin脚本编程

    Scala和Kotlin作为运行在JVM上的编程语言,解决了Java的很多痛点。今天我们来聊聊如何将Scala和Kotlin作为脚本语言使用(Java不支持以脚...

    卡尔曼和玻尔兹曼谁曼

扫码关注云+社区

领取腾讯云代金券