一、海报生成核心技术流程
1、Canvas初始化准备
- import { Canvas, } from "@tarojs/components";使用隐藏的Canvas组件(通过绝对定位移出视口):
ini
<Canvas
canvasId="posterCanvas"
style={{ position: "absolute", left: "-999px" }}
/>
- 通过
Taro.createCanvasContext
获取绘图上下文 Canvas实例获取
ini
useReady(
() => {
const ctx = Taro.createCanvasContext("posterCanvas");
canvasRef.current = ctx; }
);
2、动态二维码生成
- 使用
drawQrcode
库生成二维码图片,指定颜色和尺寸: -
- NPM 安装(推荐)看自己需要啦 我下载的2D
css
npm install weapp-qrcode --save # 基础版(适用于旧版Canvas API)
npm install weapp-qrcode-canvas-2d --save # 新版(Canvas 2D API,性能更佳)
-
- 手动引入 JS 文件 从 GitHub 下载
dist
目录中的文件:
- weapp-qrcode :weapp.qrcode.min.js
- weapp-qrcode-canvas-2d :weapp.qrcode.esm.js 将文件复制到项目
utils
目录,引入:
- 手动引入 JS 文件 从 GitHub 下载
javascript
import drawQrcode from '../../utils/weapp.qrcode.min.js';
🔗 二、核心链接
weapp-qrcode(基础版)
weapp-qrcode-canvas-2d(新版,支持 Canvas 2D)
三、生成二维码
less
const qrTempPath = await new Promise((resolve) => {
drawQrcode({ width: 100,
height: 100,
canvasId: "qrCanvas",
text: url, // 分享链接
foreground: "#1EAFB3",
callback: () => { Taro.nextTick(
() => { Taro.canvasToTempFilePath({
canvasId: "qrCanvas",
success: (res) => resolve(res.tempFilePath) },
Taro.getCurrentInstance().page);
});
}
});
});
- 使用
weapp-qrcode
库生成二维码 - 转为临时文件路径
Taro.nextTick
确保Canvas渲染完成
四、 绘制海报
scss
const ctx = Taro.createCanvasContext("posterCanvas",
Taro.getCurrentInstance().page); // 分层绘制
ctx.drawImage(bgImg.path, 0, 0, 343, 576); // 背景
ctx.drawImage(avatarImg.path, 32, 460, 48, 48); // 头像
ctx.setFontSize(16);
ctx.setFillStyle("#2F3134");
ctx.fillText(userInfo?.nickName, 80, 480); // 用户名
ctx.setFontSize(14);
ctx.setFillStyle("#8391A1");
ctx.fillText("邀您免费体验", 80, 504); // 描述
ctx.drawImage(qrImg.path, 211, 434, 100, 100); // 二维码
// 这个二维码是我上面动态生成的二维码 因为我们的需里面要求有动态的埋点二维码所以就动态生成啦 参数都在生成二维码的url上面 看你们的需求啦
五、 生成海报图片并直接掉起分享
javascript
// 4. 生成分享图
ctx.draw(false, async () => {
const { tempFilePath } = await Taro.canvasToTempFilePath({
canvasId: "posterCanvas",
quality: 1,
});
// 5. 直接调起分享
Taro.showShareImageMenu({
path: tempFilePath,
needShowEntrance: false,
success: () => {
Taro.hideLoading();
setPosterVisible(false);
},
fail: (err) => {
Taro.hideLoading();
setPosterVisible(false);
// Taro.showToast({
// title: `分享失败: ${err.errMsg}`,
// icon: "none",
// });
},
});
});
六、 整体代码示例(仅供参考)
javascript
import Taro, {
useReady,
useShareTimeline,
useShareAppMessage,
usePullDownRefresh,
} from "@tarojs/taro";
import React, { useState, useEffect, useRef } from "react";
import {
View,
Canvas,
} from "@tarojs/components";
import {
AtButton,
} from "taro-ui";
import drawQrcode from "weapp-qrcode";
import "./index.less";
export default function Mycanvas() {
const userInfo = useUserInfoStore().use.userInfo();
const canvasRef = useRef(null); //海报动态生成
const qrCanvasRef = useRef(null); // 二维码Canvas引用
const [posterVisible, setPosterVisible] = useState(false);
//Canvas实例获取
useReady(() => {
const ctx = Taro.createCanvasContext("posterCanvas", this);
canvasRef.current = ctx;
qrCanvasRef.current = Taro.createCanvasContext("qrCanvas", this);
});
useShareAppMessage(() => ({
title: "xxx",
path: `xxx&ts=${Date.now()}`, // 携带分享来源参数
imageUrl:
"xxx", // 必须为网络图片
}));
// 生成二维码
// 这里的url参数可能要加上用户的id 方便裂变
// 生成二维码
const url = `xxx&ts=${Date.now()}`;
const handleGeneratePoster = async () => {
try {
setPosterVisible(true);
Taro.showLoading({ title: "生成中..." });
// 1. 生成二维码图片
const qrTempPath = await new Promise((resolve, reject) => {
// 2. 绘制二维码(#1EAFB3)
drawQrcode({
width: 100,
height: 100,
canvasId: "qrCanvas",
text: url,
foreground: "#1EAFB3", // 二维码颜色
callback: () => {
Taro.nextTick(() => {
Taro.canvasToTempFilePath(
{
canvasId: "qrCanvas",
success: (res) => {
resolve(res.tempFilePath);
},
},
Taro.getCurrentInstance().page
);
});
},
});
});
// 并行预加载所有图片
const [bgImg, avatarImg, qrImg] = await Promise.all([
Taro.getImageInfo({
src:
"xxx",
}),
Taro.getImageInfo({
src:
"xxx",
}),
Taro.getImageInfo({
src: qrTempPath,
success: (res) => {
// console.log("res", res);
},
}),
]);
// 2. 获取Canvas上下文
const ctx = Taro.createCanvasContext(
"posterCanvas",
Taro.getCurrentInstance().page
);
// 3. 绘制元素(使用本地路径)
ctx.drawImage(bgImg.path, 0, 0, 343, 576);
ctx.drawImage(avatarImg.path, 32, 460, 48, 48);
ctx.setFontSize(16);
ctx.setFillStyle("#2F3134");
ctx.fillText(xxx, 80, 480); // y: 460+20
ctx.setFontSize(14);
ctx.setFillStyle("#8391A1");
ctx.fillText("xxx", 80, 504); // y: 480+24
ctx.drawImage(qrImg.path, 211, 434, 100, 100);
// 4. 生成分享图
ctx.draw(false, async () => {
const { tempFilePath } = await Taro.canvasToTempFilePath({
canvasId: "posterCanvas",
quality: 1,
});
// 5. 直接调起分享
Taro.showShareImageMenu({
path: tempFilePath,
needShowEntrance: false,
success: () => {
Taro.hideLoading();
setPosterVisible(false);
},
fail: (err) => {
Taro.hideLoading();
setPosterVisible(false);
// Taro.showToast({
// title: `分享失败: ${err.errMsg}`,
// icon: "none",
// });
},
});
});
} catch (error) {
setPosterVisible(false);
Taro.hideLoading();
Taro.showToast({
title: `生成失败: ${error.errMsg}`,
icon: "none",
duration: 3000,
});
}
};
return (
<View className="pageContainer">
<AtButton className="shareBtn" openType="share" onClick={handleShare}>
直接转发
</AtButton>
<AtButton
className="posterBtn"
disabled={posterVisible}
onClick={handleGeneratePoster}
>
生成海报
</AtButton>
<Canvas
style={{
position: "absolute",
left: "-999px",
width: "343px",
height: "577px",
}}
canvasId="posterCanvas"
/>
{/* 二维码的 */}
<Canvas
canvasId="qrCanvas"
style={{
position: "absolute",
left: "-9999px",
width: "100px",
height: "100px",
}}
/>
</View>
);
}
七、技术要点总结
关键技术 | 实现方式 |
---|---|
二维码生成 | weapp-qrcode + Canvas隐藏渲染 |
多图合成 | drawImage 分层绘制 + Promise.all 预加载 |
分享路径带参 | path:xxx |
微信分享菜单 | showShareImageMenu 直接调起 |
跨屏兼容处理 | 绝对定位移出视口(left: -999px ) |
高清质量保障 | quality: 1 参数 + 固定尺寸Canvas(343x577) |
八、遇到的问题
- Taro.showShareImageMenu分享朋友圈报错: {"errMsg": "showShareImageMenu:fail forbidden"} 或者 {"errMsg": "showShareImageMenu:fail upload timeout"} 因为不支持分享朋友圈带有二维码,这是俺踩的坑呜呜呜,微信基础库从3.8.2开始支持分享朋友圈,但是目前没有办法动态配置这个,也没办法隐藏这个,除非改成小程序二维码,看自己需求了

