背景描述
最近在开发一个建筑业招聘小程序,遇到一个模仿某直聘分享求职者简历的需求,根据求职者简历头像、姓名(脱敏)、掌握技能、期望工作地,生成一张分享图片。原型图效果如下。
需求梳理
- 获取到求职者简历头像、姓名(脱敏)、掌握技能、期望工作地
- 使用微信小程序canvas2d api 将内容渲染到画布的指定位置
- 文本单行溢出显示省略号
- 使用 onShareAppMessage 函数将渲染完成的图片分享至微信联系人
代码编写
wxml部分
在 wxml 文件中写入 canvas 标签,使用定位将其移动至视口范围之外,设置canvas标签宽高,onShareAppMessage 建议图片宽高比例为 5:4
<canvas type="2d" id="myCanvas" style="width: 300px; height: 240px;left:9000px;position:fixed;"></canvas>
js部分
封装一个函数,根据id
获取canvas实例
和canvas context
// 获取canvas实例和ctx画笔
getMyCanvasAndCtx(id) {
//id canvas 2d的id
return new Promise((resolve) => {
const query = wx.createSelectorQuery();
query
.select(`#${id}`)
.fields({
node: true,
size: true,
})
.exec((res) => {
const canvas = res[0].node;
const ctx = canvas.getContext("2d");
const dpr = wx.getSystemInfoSync().pixelRatio;
canvas.width = res[0].width * dpr;
canvas.height = res[0].height * dpr;
ctx.scale(dpr, dpr);
const data = {
canvas,
ctx,
};
resolve(data);
});
});
},
获取到canvas
和 canvas context
后开始进行圆形头像渲染工作
注意事项:在canvas上渲染图片,建议使用本地图片或者本地图片临时链接,如果使用网络图片可以通过wx.getImageInfo将其转换成本地图片临时链接。获取到链接后,需要使用canvas.createImage()先生成一个canvas图片实例,将获取到的图片链接赋值给实例的src属性。渲染图片的操作需要在canvas图片实例的onload回调中执行。
async initCanvas() {
const { ctx, canvas } = await this.getMyCanvasAndCtx("myCanvas");
// 画圆形头像
wx.getImageInfo({
// src请自行替换
src: this.data.resume.headPic,
success: (res) => {
// 创建canvas2d image对象
let img = canvas.createImage();
img.src = res.path;
// 图片加载完成后再画圆形头像
img.onload = () => {
this.drawRoundImage(ctx, img, 125, 30, 50, 50, 50);
};
},
});
}
drawRoundImage
渲染圆形图片封装方法,参数 ctx - canvas context, img - 图片本地临时链接, x - 图片摆放位置x坐标, y - 图片摆放位置y坐标, w - 图片宽度 h - 图片高度, r - 弧度
drawRoundImage(ctx, img, x, y, w, h, r) {
if (w < 2 * r) r = w / 2;
if (h < 2 * r) r = h / 2;
ctx.save();
ctx.beginPath();
ctx.arc(x + r, y + r, r, 0, 2 * Math.PI);
ctx.clip();
ctx.drawImage(img, x, y, w, h);
},
获取到canvas
和 canvas context
后开始进行圆形头像渲染工作
注意事项:在canvas上渲染文字,需要设置文字的字号和字体,否则有可能出现文字显示不出的问题。
async initCanvas() {
............省略渲染头像代码............
// ---------- 文字渲染从此处开始 ----------
// 文字最大宽度
const maxWidth = 270;
const { resume } = this.data;
// 画姓名
ctx.font = "24px Arial";
ctx.textAlign = "center";
ctx.fillText(resume.name, 150, 120);
// 画工种技能
if (resume.skills) {
ctx.font = "18px Arial";
ctx.textAlign = "center";
ctx.fillStyle = "#666666";
ctx.fillText(this.fittingString(ctx, resume.skills, maxWidth), 150, 160);
}
// 画期望工作地
if (resume.expectWorkLocation) {
ctx.font = "18px Arial";
ctx.textAlign = "center";
ctx.fillText(this.fittingString(ctx, resume.expectWorkLocation, maxWidth), 150, 200);
}
}
fittingString
单行文本溢出省略方法,参数 ctx - canvas context,str - 文本内容, maxWidth - 单行文本宽度,超出显示省略号
fittingString(ctx, str, maxWidth) {
// canvas measureText 方法可以得到文本的宽度
let width = ctx.measureText(str).width;
const ellipsis = "…";
let ellipsisWidth = ctx.measureText(ellipsis).width;
if (width <= maxWidth || width <= ellipsisWidth) {
return str;
} else {
let len = str.length;
while (width >= maxWidth - ellipsisWidth && len-- > 0) {
str = str.substring(0, len);
width = ctx.measureText(str).width;
}
return str + ellipsis;
}
},
保存画布,并导出画布为临时图片链接
async initCanvas() {
............省略渲染头像、渲染文字代码............
// 保存画布
ctx.save();
// sleep 500毫秒,等待图片加载完成 不然有可能圆形头像没有加载出来
await this.sleep(500);
let url = null;
// 需要自行封装 canvasToTempFilePath
const res = await wx.p.canvasToTempFilePath({ canvas });
url = res.tempFilePath;
return url;
}
sleep函数代码
sleep(time) {
return new Promise((resolve) => setTimeout(resolve, time));
},
onShareAppMessage分享,利用onShareAppMessage的promise属性对分享做了兜底处理,如果promise reject 或三秒内不 resolve,分享会使用下面传入的默认参数
onShareAppMessage 详见 onShareAppMessage 微信小程序开发者文档
onShareAppMessage(e) {
const { from } = e || {};
const promise = new Promise(async (resolve) => {
const url = await this.initCanvas();
resolve({
title: `自行替换`,
path: `自行替换`,
imageUrl: url,
});
});
// 默认参数
return {
title: `自行替换`,
path: `自行替换`,
imageUrl: "",
promise,
};
},
至此,onShareAppMessage
自定义分享图片全流程结束
生产环境效果
文本省略触发
文本省略未触发
希望能帮到大家,感恩。