Canvas 绘制海报

2,616 阅读5分钟

概述

在开发中,经常会有绘制海报的需求,再得贴个二维码什么的。接下来就和大家一起探讨下前端如何通过canvas来绘制海报。

绘制海报在我看来就是组合信息,首先你要对 canvas 有所了解,如果不会,建议先去看看文档。海报绘制的内容通常包括如下信息:

  • 海报封面
  • 用户信息
  • 二维码

如下所示:

通常后台会返回海报封面图、用户信息以及二维码跳转链接给你,这里你要做的事情就两个:

  1. 将二维码跳转链接生成二维码
  2. canvas 绘制海报

那开始行动吧。

方案

在绘制过程中,如果引用的是在线图片资源可能会遇到图片跨域问题,并且需要注意的是,canvas.toDataURL() 方法对于在线图片或者说svg等资源的支持度是不友好的。海报的绘制我并没有铺满整个屏幕,这样可能会导致图片被拉伸,我们应该拿到封面图之后获取封面图的尺寸然后根据比例绘制,所以一般绘制海报的思路大致是这样的:

  1. 请求分享相关数据
  2. 将图片资源转成base64
  3. 跳转链接通过 Qrcode.js 生成二维码
  4. 计算海报元素尺寸位置信息
  5. 创建画布绘制
  6. 将画布导出并渲染

核心

图片转base64

刚刚有提到,canvas对在线资源的支持并不是特别友好,这里主要体现在当我们需要通过 canvas.toDataURL() 导出图片的时候会出现问题,canvas认为这会污染画布,所以通常来说,我会将在线图片转成base64,这里封装了转base64的方法,可直接使用,如下所示:

function base64(url) {
 return new Promise((resolve, reject) => {
  let xhr = new XMLHttpRequest();
  xhr.open('GET', url, true);
  xhr.responseType = 'blob';
  xhr.onload = function() {
   if (this.status === 200) {
    let blob = this.response
    let fileReader = new FileReader()
    fileReader.onloadend = function(e) {
     let result = e.target.result;
     resolve(result);
    }
    fileReader.readAsDataURL(blob)
   }
  }
  xhr.onerror = function() {
   reject();
  }
  xhr.send();
 })
}

生成二维码

二维码生成这里给大家分享两种方法:

链接生成

链接生成我一般用在直接将跳转链接生成二维码并展示的情况,语法形式特别简单,如下所示:

const qrcodeSrc = 'http://qr.liantu.com/api.php?text=' + encodeURIComponent(url)

其中url为扫描二维码跳转的链接。

Qrcode.js

qrcode 传送门 >>

1. 安装

npm install --save qrcode
yarn add --save qrcode

2. 导入

import QRCode from 'qrcode'

3. 使用

QRCode.toDataURL(h5Url).then(qrCodeSrc => {
    const qrCodeImg = new Image();
    qrCodeImg.src = qrCodeSrc;
    qrCodeImg.onload = function() {
        // 将二维码绘制在海报上
    }
}).catch(err => {
    console.log(err)
})

比例计算

canvas绘制海报时最令我头疼的就是尺寸的计算啦,不过要是你掌握了一定的规律之后就很好实现了,刚刚我也有讲到,通常我在绘制海报的时候并不会让海报铺满整个屏幕,而是根据海报原图的尺寸信息结合设计图的比例关系进行绘制,避免拉伸,适应宽度即可。

所以你需要获取海报封面图的尺寸,并将其设置成canvas的尺寸,如下所示:

// 创建canvas
const oCanvas = document.createElement('canvas');
const context = oCanvas.getContext('2d');
// 将海报转成base64
this.base64(posterUrl).then(posterSrc => {
    const posterImg = new Image();
    posterImg.setAttribute('crossOrigin', 'anonymous');
    posterImg.src = posterSrc;
    posterImg.onload = () => {
        // 获取图片尺寸
        const w = posterImg.width;
        const h = posterImg.height
        // 根据图片尺寸设置canvas尺寸
        oCanvas.width  = w;
        oCanvas.height = h; 
        // 绘制
        context.drawImage(posterImg, 0, 0, w, h);
    }
})

一般来说=讲,封面是最好画的,铺满canvas即可,不用做任何处理,头疼的主要是子元素的绘制。 首先我们需要计算原图与设计图的比例:

const ratio = w / 375

其中,w 为获取到的海报宽度,375 为设计稿的宽度,一般都为375px,根据实际情况定。计算出比例之后,其他元素就比较好处理啦,如下图:

设计稿宽度为375px,二维码的尺寸为218x218,二维码距离屏幕左上角的位置分别是79px/161px,得到这些信息之后我们就可以根据比例去绘制二维码啦,如下所示:

QRCode.toDataURL(h5Url).then(qrCodeSrc => {
 const qrCodeImg = new Image();
 qrCodeImg.src = qrCodeSrc;
 qrCodeImg.onload = () => {
      // 比例 = 图片元素宽度/设计图宽度
      const ratio = w / 375;
      // 间距 = 设计图上的间距 * 比例
      const spacingX = 79 * ratio;
      const spacingY = 161 * ratio;
      // 尺寸 = 设计图上的尺寸 * 比例
      const qrSize = 218 * ratio;
      // 绘制
      context.drawImage(qrCodeImg, spacingX, spacingY, qrSize, qrSize);
 }
}).catch(err => {
 console.log(err)
})

只要掌握了根据比例计算位置尺寸的技巧,绘制海报变得如此简单,这里大家可以举一反三。海报的其他部分也根据此方式来绘制。

实现

参照如下代码:

import QRCode from 'qrcode'


{
  methods: {
    draw() {
        // 解构后台返回的海报封面图、跳转链接、用户头像
        const {  posterUrl, jumpUrl, avatar } = this.data;

        // 创建canvas
        const oCanvas = document.createElement('canvas');
        const context = oCanvas.getContext('2d');

        // 将图片资源转成base64
        Promise.all([this.base64(posterUrl), this.base64(avatar)]).then(res => {
            // 获取图片base64
            const posterSrc = res[0];
            const avatarSrc = res[1];

            // # 加载底图
            const posterImg = new Image();
            posterImg.src = posterSrc;
            posterImg.onload = () => {
                // 获取图片尺寸
                const w = posterImg.width;
                const h = posterImg.height

                // 根据图片尺寸设置canvas尺寸
                oCanvas.width  = w;
                oCanvas.height = h; 

                // 获取canvas上下文
                context.drawImage(posterImg, 0, 0, w, h);

                // # 绘制二维码 - 放置在右下角
                QRCode.toDataURL(jumpUrl).then(qrCodeSrc => {
                    const qrCodeImg = new Image();
                    qrCodeImg.src = qrCodeSrc;
                    qrCodeImg.onload = () => {
                        // 比例 = 图片元素宽度/设计图宽度
                        const ratio = w / 375;
                        // 间距 = 设计图上的间距 * 比例(右侧间距)
                        const spacing = 8 * ratio;
                        // 尺寸 = 设计图上的尺寸 * 比例
                        const qrSize = 62 * ratio;
                        // 计算x、y值
                        const x = w - qrSize - spacing;
                        const y = h - qrSize - spacing;
                        // 绘制
                        context.drawImage(qrCodeImg, x, y, qrSize, qrSize);

                        // # 绘制头像 - 头像在二维码的正中间
                        const oAvatar = new Image();
                        oAvatar.src = avatarSrc;
                        oAvatar.onload = () => {
                            const avatarSize = 12;
                            const _x = x + qrSize / 2 - avatarSize/2 * ratio;
                            const _y = y + qrSize / 2 - avatarSize/2 * ratio;
                            context.drawImage(oAvatar, _x, _y, avatarSize * ratio, avatarSize * ratio);
                            // 导出
                            this.dataURL = oCanvas.toDataURL();
                        }
                    }
                }).catch(err => {
                    console.log(err)
                })


            }
        }).catch(err => {
            console.log(err);
        })
    }
  }
}

上述代码为vue实现,仅供参考。