前言
目前所有的 APP 或者 web 页面都可以直接分享到朋友圈,从朋友圈看到一条分享动态,很难区别这条动态是公众号文章,还是一支 H5 的链接。
相比之下,在朋友圈发一张图片,会更容易吸引朋友的注意,内容也可以更直接的曝光。同时微信可以识别图片中的二维码,又能起到回流作用。
而单一的图片又会缺乏趣味性。因此,根据用户的选择,使用 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'});
高级进阶
在以上简单的封装基础上,我们其实可以加入一些更高级的用法,比如:
- 绘制圆形图片(比如头像);
- 获取绘制的文本宽度;
- 文本自动换行(原生 Canvas 绘制无自动换行功能);
- 文本域旋转等。
总结
- Canvas 图片绘制基本流程并不复杂,如果绘制的图片与文本数量较小,可以通过 Canvas 原生方法来实现;
- 如果绘制图片较多,建议使用
Promise异步加载图片; - 文本语法较多,但所有涉及到的样式是有限的,可以通过封装方法来减少记忆;
- 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
});