通过canvas生成前端海报的方案实践

8,898 阅读4分钟

目前业务中突然有需要生成简单的海报页面并且分享到微信及朋友圈的需求,由于基于目前上线时间太短没法实施,小需求被砍掉了,但是闲暇之余我还是思考了一下可行的几种方案:

1. 原生canvas画图生成海报

如下图所示,是一张简单的海报图,其构成有几个部分:底图 + 3出红框标出的区域。当前有一个问题:如果是简单的单张海报图,则可以直接渲染在 html 页面中;但是如果一张复杂的海报图由几个部分组合,需要我们在一张底图上画出其他部分,然后组合成一张完整的海报图。我们分几个步骤来生成这张完整的海报图:(源码github:github.com/llz1990/llz… 烦请各位大佬star一下~ ~)。

捕获.PNG

1.1 先生成底图

<body>
    <div class="header">
        <img src="./imgs/share.png" alt="">
    </div>
    <canvas id="myCanvas"></canvas>
</body>
const canvas = document.getElementById("myCanvas"); // 使用id来寻找canvas元素
    const cxt = canvas.getContext("2d"); // 创建context对象

    // 取可视化区域的宽、高并设置myCanvas的宽高
    const clientWidth = document.documentElement.clientWidth;
    const clientHeight = document.documentElement.clientHeight;
    canvas.width = clientWidth; // 设置myCanvas的宽
    canvas.height = clientHeight; // 设置myCanvas的高

    // 绘制一个矩形,用来做全局背景颜色
    cxt.fillStyle = "#fff";
    cxt.fillRect(0, 0, canvas.width, canvas.height); // fillRect方法是创建一个矩形,x坐标、y坐标、宽度、高度

    // 把图片绘制到myCanvas
    const img = new Image()
    img.crossOrigin = "anonymous";
    img.src = "./imgs/imgdemo04.jpeg"; // 图片路径
    img.onload = () => {
        cxt.drawImage(img, 0, 0, clientWidth, clientHeight);
        
        ...... 省略内容为绘制文字和二维码部分内容 ......
    }

1.2 绘制文字描述部分和二维码部分

// 绘制文字部分显示:
        cxt.fillStyle = "#ffffff";
        cxt.font = "14px bold 黑体";
        const str = "打开爱奇艺app,扫码领取积分,赢取豪华大礼,惊喜等着你~"
        cxt.textBaseline = "middle";
        cxt.textAlign = "left";
        let lineWidth = 0;
        let txtlimitWidth = 240; // 一行文字占用的宽度
        let initHeight = clientHeight - 50; // 绘制字体距离canvas顶部初始的高度
        let lastSubStrIndex = 0; // 每次开始截取的字符串的索引

        // 绘制文字的时候,如果当绘制的长度超出文字限制长度 txtlimitWidth, 就转行
        for (let i = 0; i < str.length; i++) {
            lineWidth += cxt.measureText(str[i]).width;
            if (lineWidth > txtlimitWidth) {
                cxt.fillText(str.substring(lastSubStrIndex, i), 20, initHeight); // 绘制截取部分
                initHeight += 20; // 20为字体的高度
                lineWidth = 0;
                lastSubStrIndex = i;
            }
            if (i == str.length - 1) { // 绘制剩余部分
                cxt.fillText(str.substring(lastSubStrIndex, i + 1), 20, initHeight);
            }
        }
        
        // 绘制二维码
        var qrcode = new Image()
        qrcode.crossOrigin = "anonymous";
        qrcode.src = "./imgs/qrcode.png" // 二维码图片路径
        qrcode.onload = () => {
            cxt.drawImage(qrcode, 300, clientHeight - 60, 50, 50);

            // 绘制特殊部位展示图
            var lightImg = new Image()
            lightImg.crossOrigin = "anonymous";
            lightImg.src = "./imgs/imgdemo05.png" // 特殊部位展示图路径
            lightImg.onload = () => {
                cxt.drawImage(lightImg, 210, 52, 72, 101);
                let _imgSrc = canvas.toDataURL("image/png", 1);
                console.log('_imgSrc =============', _imgSrc);
            }
        }

以上代码有几个需要注意的地方:

  1. 绘制图片时候,我们是实例化 Image 生成一个img 元素,在img 元素中的src属性引入本地图片路径,在图片加载完成后(即onload的回调中) 通过 ctx.drawImage 方法绘制图片;
  2. 在最后整个图片绘制完成时候,我们可以将canvas 转成土图片 base64 格式: let _imgSrc = canvas.toDataURL("image/png", 1);

1.3 解决本地服务启动的报错问题:

我们如果直接通过浏览器打开 html 页面;会有如下报错:

捕获.PNG 这个报错是浏览器跨域加载本地文件报错,可以通过插件 live-server 启动本地服务来解决。全局安装 live-server 后,启动本地服务打开html 页面,可以避免canvas 打开本地图片路径的报错:

  1. 全局安装 live-server cnpm install --save live-server

  2. 打开本地文件canvas02.html 所在目录,执行命令: live-server

此时,海报图可以正常显示。且生成了海报图的base64编码。

2. 通过引入html2canvas 插件生成海报

如下代码中,全局引入 html2canvas.min.js 文件(源码下载连接:html2canvas.hertzen.com/ )通过以下两个步骤实现:

  • 实现保存为图片的第一步:html转为canvas。基于html2canvas.js可将一个元素渲染为canvas,然后Promise对象会将截取的图片传递给参数canvas。如下代码中直接画出海报图静态html页面,然后通过全局方法实现html转canvas。

  • canvas转image。上一步生成的canvas即为包含目标元素的canvas元素对象。实现保存图片的目标只需要将canvas转image即可。基于原生canvas的toDataURL方法将canvas输出为图片base64编码。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<script src="./js/html2canvas.min.js"></script>

<body>
    <div class="container">
        <div class="header">
            <img src="./imgs/share.png" alt="">
        </div>
        <span class="desc">打开爱奇艺app,扫码领取积分,赢取豪华大礼,惊喜等着你~</span>
        <img class="code-img" src="./imgs/qrcode.png" alt="">
        <img class="hug-img" src="./imgs/imgdemo05.png" alt="">
    </div>
</body>

<script>
    html2canvas(document.querySelector(".container")).then(canvas => {
        let _imgSrc = canvas.toDataURL("image/png", 1);
        console.log('_imgSrc =============', _imgSrc);
    });
</script>