技术栈: Uniapp + Vue3
简介
因实际业务需求,需要实现微信小程序自定义分享,根据当前数据动态生成(绘制)分享卡片的图片。
基础分享使用 配置此处不在赘述,可查看上篇博客:Uniapp 微信小程序分享 - 自定义卡片内容 + 参数携带 接收
为了实现绘制分享卡片,核心需要使用 uni.createCanvasContext 。
先来看一下效果:
中间的图片绘制分四个部分:
- 卡片背景图(带小程序名称的红色边框和"马上抢")
- 用户头像
- 店铺图片
- 其余文字
注意:绘制图片必须需要专门使用uni.getImageInfo 获取图片的path后,再使用canvas.drawImage绘制,否则直接用图片路径绘制不成功,会成默认的分享图。
实现
DOM
需要
- 在DOM上创建canvas元素
- 设置 canvas-id和画布大小
- 设置样式让画布不在可视区域内。
html
<template>
<button open-type="share" class="share-btn">分享</button>
<canvas canvas-id="hoCanvas" style="width: 300px;height: 240px;position: fixed; right: -999999999rpx;"></canvas>
<template>
onShareAppMessage事件
因为获取路片路径、canvas画图会比较慢,可提前加载好固定的图片素材。
javascript
import { ref, getCurrentInstance } from 'vue'
import { getImgPath, savePoster } from './utils.js'
import { onLoad, onShareAppMessage } from '@dcloudio/uni-app';
const that = getCurrentInstance();
const shareImg = ref();
const info = ref({
shopName: '店铺名'
logoUrl: 'https://p1.meituan.net/business/a199b07951349e881d3a38b9f28b832d458281.png',
nickName: '7788',
price: 10,
num: 100,
avatar: '',
shareBg: ''
});
// 绘制分享卡片
const initShareImg = () => {
savePoster(info.value, (res) => {
shareImg.value = res;
})
}
// 页面加载就开始绘制卡片
onLoad(() => {
// 获取卡片背景path
getImgPath('https://zj-biz-gov-free-eat.oss-cn-hangzhou.aliyuncs.com/free-eat/static-example/share-activity.png', '店铺分享卡片背景',(res) => {
info.value.shareBg = res;
});
// 获取头像path
getImgPath('https://zj-biz-gov-free-eat.oss-cn-hangzhou.aliyuncs.com/free-eat/user/oR1KL7S9-m6ryWDar-1h_vaCOPXw/18860235410318-1731415218.jpg', '用户头像', (res) => {
info.value.avatar = res;
});
// 背景图 头像路径获取完了再绘制分享图
setTimeout(() => {
initShareImg();
},2000)
})
// 分享活动
onShareAppMessage((res) =>{
that.proxy.mpShare.title = `【${info.value.shopName}】开抢啦!限量${info.value.num}份,最高返${info.value.price}元!`;
that.proxy.mpShare.path = `/pages/orderPage/joinDetailMt/indexNew`;
// 绘制失败则使用微信默认的快照图片
if (shareImg.value) that.proxy.mpShare.imageUrl = shareImg.value;
});
utils 工具函数
javascript
export const fillRoundRect = (ctx, x, y, width, height, radius, img) => {
// console.log(ctx, x, y, width, height, radius, /*optional*/ fillColor)
ctx.save();
ctx.translate(x, y);
//绘制圆角矩形的各个边
drawRoundRectPath(ctx, width, height, radius);
ctx.clip();
ctx.drawImage(img, 0, 0, width, height);
// ctx.fillStyle = fillColor || "blue"; //若是给定了值就用给定的值否则给予默认值
// ctx.fill();
ctx.restore();
};
export const drawRoundRectPath = (ctx, width, height, radius) => {
ctx.beginPath(0);
//从右下角顺时针绘制,弧度从0到1/2PI
ctx.arc(width - radius, height - radius, radius, 0, Math.PI / 2);
//矩形下边线
ctx.lineTo(radius, height);
//左下角圆弧,弧度从1/2PI到PI
ctx.arc(radius, height - radius, radius, Math.PI / 2, Math.PI);
//矩形左边线
ctx.lineTo(0, radius);
//左上角圆弧,弧度从PI到3/2PI
ctx.arc(radius, radius, radius, Math.PI, Math.PI * 3 / 2);
//上边线
ctx.lineTo(width - radius, 0);
//右上角圆弧
ctx.arc(width - radius, radius, radius, Math.PI * 3 / 2, Math.PI * 2);
//右边线
ctx.lineTo(width, height - radius);
ctx.closePath();
}
// 分享卡片绘制
export const savePoster = (info, callback) => {
const { logoUrl, nickName, price, num, shareBg, avatar } = info;
const canvas = uni.createCanvasContext('hoCanvas');
uni.getImageInfo({
src: logoUrl,
success: (logoRes) => {
canvas.drawImage(shareBg, 0, 0, 300, 240);
fillRoundRect(canvas, 10, 37, 160, 160, 10, logoRes.path);
canvas.setFontSize(12);
canvas.setFillStyle("#333");
// canvas.setStrokeStyle('#333');
canvas.fillText('最高返', 180, 88);
canvas.setFontSize(12);
canvas.setFillStyle("#FD3540");
canvas.setStrokeStyle('#FD3540');
canvas.fillText('¥', 216, 88);
canvas.setFontSize(24);
canvas.setFillStyle("#FD3540");
canvas.setStrokeStyle('#FD3540');
canvas.fillText(price, 225, 88);
canvas.setFontSize(16);
canvas.setFillStyle("#3D3D3D");
canvas.setStrokeStyle('#3D3D3D');
canvas.fillText('霸王餐', 174, 110);
canvas.setFontSize(16);
canvas.setFillStyle("#FD3540");
canvas.setStrokeStyle('#FD3540');
canvas.fillText(`限量${num}份!`, 222, 110);
fillRoundRect(canvas, 4, 210, 22, 22, 11, avatar);
canvas.setFontSize(12);
canvas.setFillStyle("#FFFFFF");
canvas.setStrokeStyle('#FFFFFF');
canvas.fillText(`${nickName}为您推荐`, 30, 225);
canvas.draw(false, () => {
// 获取 canvas 的临时路径
uni.canvasToTempFilePath({
canvasId: 'hoCanvas',
success: (tempFilePathRes) => {
const tempFilePath = tempFilePathRes.tempFilePath;
callback(tempFilePath);
console.log('临时路径:', tempFilePath);
},
fail: (err) => {
console.error('获取临时路径失败:', err);
}
});
});
},
fail: (err) => {
console.error('获取分享图片信息失败:', err);
message(`获取店铺图片信息失败,请稍后重试`)
}
});
}
// 获取图片path【店铺分享背景、用户头像】
export const getImgPath = (img, name, callback) => {
uni.getImageInfo({
src: img,
success: (imgRes) => {
callback(imgRes.path);
},
fail: (err) => {
console.error(`获取${name}图片信息失败,请稍后重试`, err);
}
})
}