南顺网络

扫一扫微信二维码

如何简化 Canvas 图片绘制

南顺网络 2020-06-24 14:30 经验之谈 59

目前所有的 APP 或者 web 页面都可以直接分享到朋友圈发一条链接与图片情况下,相比之下,在朋友圈发一张图片,会更容易吸引朋友的注意,内容也可以更直接的曝光。同时微信可以识别图片中的二维码,又能起到回流作用。而单一的图片又会乏趣味性。因此,根据用户的选择,使用 Canvas 生成自定义的海报图,成为了目前主流的 H5 类别。

如何使用 Canvas 绘制图片

了解以下 3 个步骤,就可以完成绘制了:

1. 初始化 Canvas 并获取 Canvas 绘制的上下文:

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');

2. 绘制图片、文字:

ctx.drawImage(imageElement,0,0);
ctx.fillText("Hello world", 10, 50);

3. 绘制结束,导出 base64:

const imageBase64 = canvas.toDataURL();

实际绘制中存在的问题

以上的步骤为绘制的关键代码,但实际绘制过程中还是会存在一些问题的:

1. 图片需要在 load 之后才能绘制:

const imageElement = new Image();
imageElement.onload = ()=>{
    ctx.drawImage(imageElement,0,0);
}
imageElement.src = imageAssetsURL;
那如果要绘制两张图片呢?
const imageElement = new Image();
imageElement.onload = ()=>{
    ctx.drawImage(imageElement,0,0);
    
    // 第一张绘制结束,开始绘制第二张图片
    const imageElementSecond = new Image();
    imageElementSecond.onload = ()=>{
        ctx.drawImage(imageElementSecond,0,0);
    }
    imageElementSecond.src = imageAssetsURLSecond;
}
imageElement.src = imageAssetsURL;
那如果要绘制 10 张图片呢?

2. 图片加载未优化:

使用回调,第二张图片,只能等第一张图片绘制完成后才能开始加载。

图片资源是不是可以同时开始加载?

3. 文本绘制语法繁杂:

ctx.save();
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.font = "36px Arial";
ctx.fillStyle = "#ffffff";
ctx.fillText("Hello world", 10, 50);
ctx.restore();

需要记忆的语法很多,如果要文本要加粗怎么设置?

简化并优化绘制过程

1. 简化回调

先 load 所有图片,图片加载完成后统一绘制。

2. 优化加载

使用 Promise 异步加载图片,让所有图片同时开始加载:

function loadImages(...assetsArr){
    const imageArr = [];
    const promiseArr = [];
    assetsArr.forEach((item,index)=>{
        promiseArr.push(new Promise((resolve)=>{
            const image = new Image();
            imageArr.push(image);
            image.onload = ()=>{
                resolve(imageArr)
            };
            image.src = assetsArr[index];
        }));
    });
    return Promise.all(promiseArr);
}

3. 简化文本绘制

将文本绘制的内容、字体、行高等所有的基本样式封装成方法,通过修改对象参数来代替语法规则:

// 初始化默认值
const defaultFontStyle = {
    fontStyle:'normal',
    fontVariant:'normal',
    fontWeight:'normal',
    fontSize: 30,
    lineHeight: 'normal',
    fontFamily: 'Arial',
    left: 0,
    top: 300,
    maxWidth:undefined,
    content:'',
    textAlign:'start',
    textBaseline:'alphabetic',
    direction:'inherit',
    color: '#000000',
};

// 将文本绘制语法封装
drawText(obj){
    const prop = Object.assign({},defaultFontStyle,obj)
    ctx.save();
    ctx.fillStyle = prop.color;
    ctx.font = `${prop.fontStyle} ${prop.fontVariant} ${prop.fontWeight} ${prop}px/${prop.lineHeight} ${prop.fontFamily}`;
    ctx.textBaseline = prop.textBaseline;
    ctx.textAlign = prop.textAlign;
    ctx.direction = prop.direction;
    ctx.fillText(item,prop.left,prop.top,prop.maxWidth);
    ctx.restore();
}

// 使用
drawText({content:'Hello world'});

高级进阶

在以上简单的封装基础上,我们其实可以加入一些更高级的用法,比如:

    1. 绘制圆形图片(比如头像);
    2. 获取绘制的文本宽度;
    3. 文本自动换行(原生 Canvas 绘制无自动换行功能);
    4. 文本域旋转等。

总结

    1. Canvas 图片绘制基本流程并不复杂,如果绘制的图片与文本数量较小,可以通过 Canvas 原生方法来实现;
    2. 如果绘制图片较多,建议使用 Promise 异步加载图片;
    3. 文本语法较多,但所有涉及到的样式是有限的,可以通过封装方法来减少记忆;
    4. H5 海报绘制基本上只包含 图片 和 文本 绘制,可以将常用方法封装成组件,方便重复使用。

封装组件

本文提到的内容,包括高级进阶,可以 点此 查看源码。

如果需要直接使用,可以通过 npm 直接安装,使用说明如下:

npm 安装

npm i create-picture --save

使用

import CreatePicture from 'create-picture';

// 初始化
const cp = new CreatePicture();

// 初始化(传参)
const cp:CreatePicture = new CreatePicture({width:750,height:1448});

// 绘制图片,参数1为图片路径,其他参数与 CanvasRenderingContext2D.drawImage() 参数相同
cp.drawImage(require('../assets/save_bg.jpg'),0,0);

// 绘制文本
cp.drawText({content:'文本'});

// 绘制文本,获取文字宽度
const textWidth = cp.drawText({
    content:'文本',
    fontSize: 30,
    top: 300,
    color: '#ffffff',
});

// 获取合成图
cp.getPicture().then((picture)=>{
    // picture 为合成的 base 64
});