【taro react】---- 兼容微信小程序和H5的海报绘制插件
发布时间
阅读量:
阅读量
该海报绘制组件是一个基于React和Taro.js开发的跨环境(Web与微信)海报制作工具。其主要功能包括:
初始化与状态管理
- 接收海报参数(如背景类型、尺寸、坐标等)
- 初始化状态变量(如是否已开始绘制)
创建画布- 在浏览器环境中获取并渲染canvas
- 提供重复绘制机制以解决跨设备兼容性问题
核心绘图功能- 绘制背景图像
- 绘制矩形
- 绘制文字
- 处理圆角区域
- 多种绘图样式(图片、文本)
动态管理与导出- 实时更新进度
- 提供下载图片功能
- 导出生成海报至相册
辅助功能- 图表统计分析
- 日期时间格式化
- 高亮显示未完成任务
该组件通过Taro API实现跨环境适配,并支持复杂的绘图逻辑和动态管理机制。
1. 环境
- taro 3.2.12
- taro react 【Class Component】
2. 预览
- H5版式视觉预览测试
- 微信小程序版式预览测试

3. 实现
1. 创建标签
<View className="rui-canvas-container">
<Canvas
type="2d"
className="canvas"
style={`width:${shareInfo.width}px;height:${shareInfo.height}px;margin-bottom:20px;`}
canvasId={canvasId}
id={canvasId}
></Canvas>
</View>
注意: type=“2d”!!! type=“2d”!!! type=“2d”!!!
2. 初始化海报的大小和绘制对象总数
let { shareInfo } = this.props;
this.width = shareInfo.width || 450;
this.height = shareInfo.height || 450;
this.count = 0;
this.total = this.countConfigList(shareInfo);
3. 计算需要绘制的list的总量
// 计算需要绘制的list的总量
countConfigList(item, total = 1) {
(function allTotal(item) {
if (item && item.list && Array.isArray(item.list) && item.list.length) {
total += item.list.length;
item.list.forEach(allTotal);
}
})(item);
return total;
}
4. 获取对应的上下文对象
componentDidMount() {
Taro.nextTick(() => {
this.create();
});
}
create(){
const dpr = (this.dpr = Taro.getSystemInfoSync().pixelRatio);
if (Taro.getEnv() === Taro.ENV_TYPE.WEB) {
const divElement = document.querySelector(`#${this.state.canvasId}`);
divElement.style = `width:${this.width * dpr}px;height:${
this.height * dpr
}px;margin-bottom:20px;`;
this.canvas = divElement.children[0];
if (!this.canvas) {
let timer = setTimeout(() => {
this.create();
clearTimeout(timer);
});
return false;
}
this.canvas.width = this.width * dpr;
this.canvas.height = this.height * dpr;
this.ctx = this.canvas.getContext("2d");
this.ctx.scale(dpr, dpr);
this.draw();
} else {
// 小程序环境
const page = Taro.getCurrentInstance().page;
Taro.createSelectorQuery()
.in(page)
.select(`#${this.state.canvasId}`)
.fields({
node: true,
size: true,
})
.exec((res) => {
if (!res[0] || !res[0].node) {
let timer = setTimeout(() => {
this.create();
clearTimeout(timer);
});
return console.error("商品海报获取不到 canvas 标签!");
}
const { node, width, height } = res[0];
const canvas = (this.canvas = node);
this.ctx = canvas.getContext("2d");
canvas.width = width * dpr;
canvas.height = height * dpr;
this.ctx.scale(dpr, dpr);
this.draw();
});
}
}
在操作过程中需要注意的是,有时会遇到无法正常获取canvas的情况。为了提高效率采用递归方法进行处理,并持续调用脚本进行尝试,直到成功加载canvas为止。同时,在成功加载画布后并设置画布的宽度等参数
5. 开始绘制
// 绘制海报
draw() {
let { shareInfo } = this.props;
if (shareInfo.type == "img") {
this.downloadImage(shareInfo.pic)
.then((src) => {
shareInfo.path = src;
this.drawIMage(
shareInfo,
this.ctx,
this.hasDrawOverAndLoopDraw.bind(this, shareInfo)
);
})
.catch((err) => {
this.drawRect(
shareInfo,
this.ctx,
this.hasDrawOverAndLoopDraw.bind(this, shareInfo)
);
});
} else {
this.drawRect(
shareInfo,
this.ctx,
this.hasDrawOverAndLoopDraw.bind(this, shareInfo)
);
}
}
将海报的初始背景单独绘制!!!
6. 公用判断和继续绘制
// 提取公用判断和继续绘制
hasDrawOverAndLoopDraw(item) {
this.hasDrawOver();
this.loopDraw(item.list);
}
7. 判断是否绘制完成
// 判断是否绘制完成
hasDrawOver() {
this.count++;
if (this.count >= this.total) {
console.log("绘制完成");
let timer = setTimeout(() => {
const { onCreateSuccess = () => {} } = this.props;
let imageBase64 = this.canvas.toDataURL("image/png", 1);
onCreateSuccess({
errMsg: "canvasToTempFilePath:ok",
tempFilePath: imageBase64,
});
this.state.filePath = imageBase64;
clearTimeout(timer);
}, 30);
}
}
8. 循环绘制
// 循环绘制
loopDraw(list) {
if (list && Array.isArray(list)) {
list.forEach((item) => {
if (item.type == "img") {
this.downloadImage(item.pic)
.then((src) => {
item.path = src;
this.drawIMage(
item,
this.ctx,
this.hasDrawOverAndLoopDraw.bind(this, item)
);
})
.catch((err) => {
this.drawRect(
item,
this.ctx,
this.hasDrawOverAndLoopDraw.bind(this, item)
);
});
} else if (item.type == "rect") {
this.drawRect(
item,
this.ctx,
this.hasDrawOverAndLoopDraw.bind(this, item)
);
} else if (item.type == "text") {
this.drawText(
item,
this.ctx,
this.hasDrawOverAndLoopDraw.bind(this, item)
);
}
});
}
}
9. 下载图片
downloadImage(src) {
return new Promise((resolve, reject) => {
if (src.startsWith("#")) {
resolve(src);
return;
}
let img =
Taro.getEnv() === Taro.ENV_TYPE.WEB
? new Image()
: this.canvas.createImage();
if (Taro.getEnv() === Taro.ENV_TYPE.WEB) {
img.setAttribute("crossOrigin", "anonymous");
}
img.onload = function () {
resolve(img);
};
img.onerror = () => reject(`下载图片失败 ${src}`);
img.src = src;
});
}
10. 绘制图片
// 绘制图片方法
drawIMage(opts, ctx, callback = () => {}) {
if (opts.borderRadius) {
this.drawBorderRadius(opts, ctx);
}
ctx.drawImage(opts.path, opts.x, opts.y, opts.width, opts.height);
ctx.save(true);
callback();
}
11. 绘制矩形
// 绘制矩形
drawRect(item, ctx, callback = () => {}) {
let { color = "#fff", x = 0, y = 0, width = 100, height = 100 } = item;
if (item.borderRadius) {
this.drawBorderRadius(item, ctx);
}
ctx.fillStyle = color;
ctx.fillRect(x, y, width, height);
ctx.save(true);
callback();
}
12. 绘制文字
// 绘制文字
drawText(opts, ctx, callback = () => {}) {
let textArr = [];
let len = opts.len;
let rows = opts.rows ? opts.rows - 1 : 2;
let clen = 2 * len;
let h = (function (text) {
let k = 0;
let strLength = 0;
for (let j = 0; j < text.length; j++) {
if (text.charCodeAt(j) > 255) {
strLength += 2;
} else {
strLength++;
}
if (
strLength % clen == 0 ||
(strLength % clen == clen - 1 &&
text[j + 1] &&
text.charCodeAt(j + 1) > 255)
) {
textArr.push({
title: text.substring(k, j),
index: strLength,
});
k = j;
}
if (
j == text.length - 1 &&
textArr.indexOf(text.substring(k)) == -1
) {
textArr.push({
title: text.substring(k),
index: strLength,
});
}
}
return {
len: strLength,
arr: textArr,
};
})(opts.title);
let fontWeight = opts.fontWeight || "normal";
ctx.textAlign = opts.textAlign || "left"; // 文字居中
ctx.textBaseline = opts.textBaseline || "normal";
ctx.fillStyle = opts.color || "#000"; // 文字颜色:黑色
ctx.font = `${fontWeight} ` + opts.fontSize + "px Arial"; // 文字字号:20px
for (let i = 0; i < h.arr.length; i++) {
let title = h.arr[i].title;
let index = h.arr[i].index;
if (i < rows) {
ctx.fillText(title, opts.x, opts.y + i * opts.space, opts.width);
} else if (i == rows) {
if (index % clen == 0 || index % clen > clen - 4) {
ctx.fillText(
title.substring(0, title.length - 3) + "...",
opts.x,
opts.y + i * opts.space,
opts.width
);
} else if (index % clen < clen - 2) {
ctx.fillText(title, opts.x, opts.y + i * opts.space, opts.width);
}
} else {
break;
}
}
ctx.save(true);
callback();
}
13. 绘制圆角
//绘制圆角
drawBorderRadius(opts, ctx) {
let css = {
borderRadius: 0,
width: 100,
height: 100,
x: 0,
y: 0,
};
css = { ...css, ...opts };
if (css.borderRadius && css.width && css.height) {
const r = Math.min(css.borderRadius, css.width / 2, css.height / 2);
const width = css.width;
const height = css.height;
const x = css.x;
const y = css.y;
ctx.beginPath();
ctx.save(true);
ctx.strokeStyle = "transparent";
ctx.arc(r + x, r + y, r, 1 * Math.PI, 1.5 * Math.PI);
ctx.lineTo(r + x, 0 + y);
ctx.arc(width - r + x, r + y, r, 1.5 * Math.PI, 2 * Math.PI);
ctx.lineTo(width + x, height - r + y);
ctx.arc(width - r + x, height - r + y, r, 0, 0.5 * Math.PI);
ctx.lineTo(width - r + x, height + y);
ctx.arc(r + x, height - r + y, r, 0.5 * Math.PI, 1 * Math.PI);
ctx.closePath();
ctx.stroke();
ctx.clip();
ctx.save(true);
}
}
14. 完整代码
/* * @Descripttion:
* @version: 1.0.0
* @Author: Rattenking
* @Date: 2022-05-23 09:26:40
* @LastEditors: Rattenking
*/
import React, { Component } from "react";
import { Canvas, View } from "@tarojs/components";
import Taro from "@tarojs/taro";
import "./index.scss";
export default class RuiSelfPoster extends Component {
constructor(props) {
super(props);
let { shareInfo } = this.props;
this.width = shareInfo.width || 450;
this.height = shareInfo.height || 450;
this.count = 0;
this.total = this.countConfigList(shareInfo);
}
state = {
canvasId: "ruiSelfPoster",
scale: 750,
width: 300,
height: 300,
filePath: "",
};
componentDidMount() {
Taro.nextTick(() => {
this.create();
});
}
// 计算需要绘制的list的总量
countConfigList(item, total = 1) {
(function allTotal(item) {
if (item && item.list && Array.isArray(item.list) && item.list.length) {
total += item.list.length;
item.list.forEach(allTotal);
}
})(item);
return total;
}
// 获取对应的上下文对象
create() {
const dpr = (this.dpr = Taro.getSystemInfoSync().pixelRatio);
if (Taro.getEnv() === Taro.ENV_TYPE.WEB) {
const divElement = document.querySelector(`#${this.state.canvasId}`);
divElement.style = `width:${this.width * dpr}px;height:${
this.height * dpr
}px;margin-bottom:20px;`;
this.canvas = divElement.children[0];
if (!this.canvas) {
let timer = setTimeout(() => {
this.create();
clearTimeout(timer);
});
return false;
}
this.canvas.width = this.width * dpr;
this.canvas.height = this.height * dpr;
this.ctx = this.canvas.getContext("2d");
this.ctx.scale(dpr, dpr);
this.draw();
} else {
// 小程序环境
const page = Taro.getCurrentInstance().page;
Taro.createSelectorQuery()
.in(page)
.select(`#${this.state.canvasId}`)
.fields({
node: true,
size: true,
})
.exec((res) => {
if (!res[0] || !res[0].node) {
let timer = setTimeout(() => {
this.create();
clearTimeout(timer);
});
return console.error("商品海报获取不到 canvas 标签!");
}
const { node, width, height } = res[0];
const canvas = (this.canvas = node);
this.ctx = canvas.getContext("2d");
canvas.width = width * dpr;
canvas.height = height * dpr;
this.ctx.scale(dpr, dpr);
this.draw();
});
}
}
// 绘制海报
draw() {
let { shareInfo } = this.props;
if (shareInfo.type == "img") {
this.downloadImage(shareInfo.pic)
.then((src) => {
shareInfo.path = src;
this.drawIMage(
shareInfo,
this.ctx,
this.hasDrawOverAndLoopDraw.bind(this, shareInfo)
);
})
.catch((err) => {
this.drawRect(
shareInfo,
this.ctx,
this.hasDrawOverAndLoopDraw.bind(this, shareInfo)
);
});
} else {
this.drawRect(
shareInfo,
this.ctx,
this.hasDrawOverAndLoopDraw.bind(this, shareInfo)
);
}
}
// 循环绘制
loopDraw(list) {
if (list && Array.isArray(list)) {
list.forEach((item) => {
if (item.type == "img") {
this.downloadImage(item.pic)
.then((src) => {
item.path = src;
this.drawIMage(
item,
this.ctx,
this.hasDrawOverAndLoopDraw.bind(this, item)
);
})
.catch((err) => {
this.drawRect(
item,
this.ctx,
this.hasDrawOverAndLoopDraw.bind(this, item)
);
});
} else if (item.type == "rect") {
this.drawRect(
item,
this.ctx,
this.hasDrawOverAndLoopDraw.bind(this, item)
);
} else if (item.type == "text") {
this.drawText(
item,
this.ctx,
this.hasDrawOverAndLoopDraw.bind(this, item)
);
}
});
}
}
// 提取公用判断和继续绘制
hasDrawOverAndLoopDraw(item) {
this.hasDrawOver();
this.loopDraw(item.list);
}
// 判断是否绘制完成
hasDrawOver() {
this.count++;
if (this.count >= this.total) {
console.log("绘制完成");
let timer = setTimeout(() => {
const { onCreateSuccess = () => {}, onCreateFail = () => {} } =
this.props;
let imageBase64 = this.canvas.toDataURL("image/png", 1);
onCreateSuccess({
errMsg: "canvasToTempFilePath:ok",
tempFilePath: imageBase64,
});
this.state.filePath = imageBase64;
// this.getTempFile(null);
clearTimeout(timer);
}, 30);
}
}
downloadImage(src) {
return new Promise((resolve, reject) => {
if (src.startsWith("#")) {
resolve(src);
return;
}
let img =
Taro.getEnv() === Taro.ENV_TYPE.WEB
? new Image()
: this.canvas.createImage();
if (Taro.getEnv() === Taro.ENV_TYPE.WEB) {
img.setAttribute("crossOrigin", "anonymous");
}
img.onload = function () {
resolve(img);
};
img.onerror = () => reject(`下载图片失败 ${src}`);
img.src = src;
});
}
//保存海报
saveToAlbum(filePath = "") {
return new Promise((resolve, reject) => {
if (!this.state.filePath || this.state.filePath == "") {
this.$api._toast("正在生成海报,请稍后保存!");
reject("正在生成海报,请稍后保存!");
return;
}
filePath = filePath || this.state.filePath;
let data = filePath.slice(22);
const fs = wx.getFileSystemManager();
const envPath = wx.env.USER_DATA_PATH;
let path = `${envPath}/${+new Date()}.png`;
fs.writeFile({
filePath: path,
data: data,
encoding: "base64",
success: (res) => {
wx.saveImageToPhotosAlbum({
filePath: path,
success: function (res) {
resolve(res);
fs.readdir({
dirPath: envPath,
success(res) {
res.files.forEach((el) => {
// 遍历文件列表里的数据 (把不是.png后缀的过滤掉)
if (el.includes(".png")) {
// 删除存储的垃圾数据
// 这里注意。文件夹也要加上,如果直接文件名的话会无法找到这个文件
fs.unlink({
filePath: `${envPath}/${el}`,
fail(e) {
console.log("readdir文件删除失败:", e);
},
});
}
});
},
});
},
fail: function (err) {
console.log(err);
reject(err);
},
});
},
fail: (err) => {
console.log(err);
reject(err);
},
});
});
}
render() {
let { canvasId } = this.state;
let { shareInfo } = this.props;
return (
<View className="rui-canvas-container">
<Canvas
type="2d"
className="canvas"
style={`width:${shareInfo.width}px;height:${shareInfo.height}px;margin-bottom:20px;`}
canvasId={canvasId}
id={canvasId}
></Canvas>
</View>
);
}
// 绘制矩形
drawRect(item, ctx, callback = () => {}) {
let { color = "#fff", x = 0, y = 0, width = 100, height = 100 } = item;
if (item.borderRadius) {
this.drawBorderRadius(item, ctx);
}
ctx.fillStyle = color;
ctx.fillRect(x, y, width, height);
ctx.save(true);
callback();
}
//绘制圆角
drawBorderRadius(opts, ctx) {
let css = {
borderRadius: 0,
width: 100,
height: 100,
x: 0,
y: 0,
};
css = { ...css, ...opts };
if (css.borderRadius && css.width && css.height) {
const r = Math.min(css.borderRadius, css.width / 2, css.height / 2);
const width = css.width;
const height = css.height;
const x = css.x;
const y = css.y;
ctx.beginPath();
ctx.save(true);
ctx.strokeStyle = "transparent";
ctx.arc(r + x, r + y, r, 1 * Math.PI, 1.5 * Math.PI);
ctx.lineTo(r + x, 0 + y);
ctx.arc(width - r + x, r + y, r, 1.5 * Math.PI, 2 * Math.PI);
ctx.lineTo(width + x, height - r + y);
ctx.arc(width - r + x, height - r + y, r, 0, 0.5 * Math.PI);
ctx.lineTo(width - r + x, height + y);
ctx.arc(r + x, height - r + y, r, 0.5 * Math.PI, 1 * Math.PI);
ctx.closePath();
ctx.stroke();
ctx.clip();
ctx.save(true);
}
}
// 绘制图片方法
drawIMage(opts, ctx, callback = () => {}) {
if (opts.borderRadius) {
this.drawBorderRadius(opts, ctx);
}
ctx.drawImage(opts.path, opts.x, opts.y, opts.width, opts.height);
ctx.save(true);
callback();
}
// 绘制文字
drawText(opts, ctx, callback = () => {}) {
let textArr = [];
let len = opts.len;
let rows = opts.rows ? opts.rows - 1 : 2;
let clen = 2 * len;
let h = (function (text) {
let k = 0;
let strLength = 0;
for (let j = 0; j < text.length; j++) {
if (text.charCodeAt(j) > 255) {
strLength += 2;
} else {
strLength++;
}
if (
strLength % clen == 0 ||
(strLength % clen == clen - 1 &&
text[j + 1] &&
text.charCodeAt(j + 1) > 255)
) {
textArr.push({
title: text.substring(k, j),
index: strLength,
});
k = j;
}
if (
j == text.length - 1 &&
textArr.indexOf(text.substring(k)) == -1
) {
textArr.push({
title: text.substring(k),
index: strLength,
});
}
}
return {
len: strLength,
arr: textArr,
};
})(opts.title);
let fontWeight = opts.fontWeight || "normal";
ctx.textAlign = opts.textAlign || "left"; // 文字居中
ctx.textBaseline = opts.textBaseline || "normal";
ctx.fillStyle = opts.color || "#000"; // 文字颜色:黑色
ctx.font = `${fontWeight} ` + opts.fontSize + "px Arial"; // 文字字号:20px
for (let i = 0; i < h.arr.length; i++) {
let title = h.arr[i].title;
let index = h.arr[i].index;
if (i < rows) {
ctx.fillText(title, opts.x, opts.y + i * opts.space, opts.width);
} else if (i == rows) {
if (index % clen == 0 || index % clen > clen - 4) {
ctx.fillText(
title.substring(0, title.length - 3) + "...",
opts.x,
opts.y + i * opts.space,
opts.width
);
} else if (index % clen < clen - 2) {
ctx.fillText(title, opts.x, opts.y + i * opts.space, opts.width);
}
} else {
break;
}
}
ctx.save(true);
callback();
}
}
4. 使用
1. 参数
1.1 参数说明
| 参数名 | 参数说明 |
|---|---|
| shareInfo | 海报的绘制参数 |
| ref | 获取组件,用于保存海报使用 |
| onCreateSuccess | 绘制完整回调函数 |
1.2 shareInfo说明
| 参数名 | 参数说明 | 参数值说明 | 默认值 |
|---|---|---|---|
| type | 海报背景的类型 | ‘img’或者’rect’ | ‘rect’ |
| pic | 海报背景图(只有type='img’有效) | ‘’ | |
| width | 海报的宽 | Number | 450 |
| height | 海报的高 | Number | 450 |
| x | 海报的背景起始坐标x | Number | 0 |
| y | 海报的背景起始坐标y | Number | 0 |
| list | 海报中的其他内容 | Arrary | [] |
1.3 list 说明
| 参数名 | 参数说明 | 参数值说明 | 默认值 |
|---|---|---|---|
| value | 海报内容以对象进行设置(数组的值可能是文字,图片和长方形) | Object |
1.3.1 rect 长方形参数说明
| 参数名 | 参数说明 | 参数值说明 | 默认值 |
|---|---|---|---|
| type | 绘制长方形 | String | ‘rect’ |
| color | 绘制长方形的颜色 | String | ‘#fff’ |
| width | 绘制长方形的宽 | Number | 100 |
| height | 绘制长方形的高 | Number | 100 |
| x | 绘制长方形的起始坐标x | Number | 0 |
| y | 绘制长方形的起始坐标y | Number | 0 |
| borderRadius | 绘制长方形的圆角 | Number | |
| list | 绘制长方形中的内容(注意次参数和shareInfo的list参数等同解析) | Array |
1.3.2 img 长方形参数说明
| 参数名 | 参数说明 | 参数值说明 | 默认值 |
|---|---|---|---|
| type | 绘制图片 | String | ‘img’ |
| pic | 绘制图片的地址 | String | ‘’ |
| width | 绘制图片的宽 | Number | |
| height | 绘制图片的高 | Number | |
| x | 绘制图片的起始坐标x | Number | |
| y | 绘制图片的起始坐标y | Number | |
| borderRadius | 绘制图片的圆角 | Number | |
| list | 绘制图片中的内容(注意次参数和shareInfo的list参数等同解析) | Array |
1.3.3 text 长方形参数说明
| 参数名 | 参数说明 | 参数值说明 | 默认值 |
|---|---|---|---|
| type | 绘制文字 | String | ‘text’ |
| title | 绘制的文字 | String | ‘’ |
| x | 绘制文字的起始坐标x | Number | |
| y | 绘制文字的起始坐标y | Number | |
| len | 绘制一行文字的个数 | Number | |
| rows | 绘制文字的行数 | Number | 2 |
| fontWeight | 绘制文字的字体的粗细 | String | ‘normal’ |
| textAlign | 绘制文字横向显示位置 | String | ‘left’ |
| textBaseline | 绘制文字纵向显示位置 | String | ‘normal’ |
| color | 绘制文字的颜色 | String | ‘#000’ |
| fontSize | 绘制文字的字体大小 | Number | |
| space | 绘制文字的行间距 | Number | |
| list | 绘制图片中的内容(注意次参数和shareInfo的list参数等同解析) | Array |
2. 实例
2.1 引入组件
import RuiSelfPoster from "@com/RuiSelfPoster/RuiSelfPoster";
2.2 配置海报参数
let shareConfig = {
type: "img",
width: 520,
height: 520,
pic: bgUrl,
x: 0,
y: 0,
list: [
{
type: "text",
color: "#ffffff",
rows: 2,
len: 8,
space: 45,
fontSize: 34,
textAlign: "center",
textBaseline: "top",
title: hallInfo.hallname,
y: 36,
x: 260,
width: 280,
},
{
type: "text",
color: "#fdddd0",
rows: 1,
len: 14,
space: 40,
fontSize: 20,
title: `联系电话: ${hallInfo.phone || hallInfo.linktel}`,
y: 445,
x: 260,
width: 205,
},
{
type: "text",
color: "#fdddd0",
rows: 1,
len: 14,
space: 40,
fontSize: 20,
title: `店铺ID: ${hallInfo.tyfoShopid}`,
y: 445,
x: 55,
width: 205,
},
{
type: "text",
color: "#fdddd0",
rows: 1,
len: 23,
space: 40,
fontSize: 20,
title: `店铺地址: ${hallInfo.halladdress}`,
y: 475,
x: 55,
width: 410,
list: [
{
type: "rect",
color: "#fff",
x: 113,
y: 127,
height: 294,
width: 294,
borderRadius: 24,
list: [
{
type: "img",
width: 288,
height: 288,
pic: code || this.$icon.staticicon.commonPosterQRCode,
x: 116,
y: 130,
},
],
},
],
},
],
};
this.setState({
canvasStatus: true,
config: shareConfig,
});
2.3 使用组件
constructor(props) {
super(props);
this.childRef = null;
}
render() {
let { close = () => {} } = this.props;
let { canvasStatus, filePath, config } = this.state;
return (
<React.Fragment>
<RuiMask
close={(e) => {
close.call(this);
this.setState({ canvasStatus: true, shareImage: "" });
}}
zIndex={10242}
>
<View>
{/* 绘制完成图片显示 */}
{filePath && !canvasStatus ? (
<React.Fragment>
<View className="rui-poster-content">
<Image className="rui-share-image" src={filePath} lazy-load />
<View className="rui-flex-cc">
{this.$api.hasWeapp() ? (
<View
className="rui-save-btn"
onClick={this.saveToAlbum.bind(this)}
>
保存到相册
</View>
) : (
<View className="rui-save-btn rui-pr">
<Image src={filePath} className="rui-save-img"></Image>
<Text>长按保存图片</Text>
</View>
)}
</View>
</View>
</React.Fragment>
) : (
<React.Fragment />
)}
{/* // 由于部分限制,目前组件通过状态的方式来动态加载 */}
{canvasStatus ? (
<View className="rui-poster-content">
<RuiSelfPoster
className="rui-share-image"
ref={this.setChildRef}
onCreateSuccess={this.onCreateSuccess.bind(this)}
shareInfo={config}
/>
</View>
) : (
<React.Fragment />
)}
</View>
</RuiMask>
</React.Fragment>
);
}
// 设置Ref
setChildRef = (ref) => {
if (ref) {
this.childRef = ref;
}
};
2.4 海报绘制成功导出base64地址
onCreateSuccess(result) {
const { tempFilePath, res, errMsg } = result;
// console.log(tempFilePath);
if (
res === "canvasToTempFilePath:ok" ||
errMsg === "canvasToTempFilePath:ok"
) {
this.setState({ filePath: tempFilePath, canvasStatus: false });
}
}
2.5 保存海报
saveToAlbum() {
let { close = () => {} } = this.props;
this.childRef
.saveToAlbum()
.then((res) => {
this.$api._toast("保存成功!");
close.call(this);
// this.$event.closeLayer.call(this, "isShowPoster");
})
.catch((err) => console.log(err));
}
5. 总结
- 此处绘制海报没有对海报进行缩放处理,而是按照海报的原尺寸在屏幕之外进行的绘制,当绘制完成,导出base64地址,在img标签中显示图片!
- 此处保存海报使用的是保存base64的方式,保存图片,所以注意清空其他图片!
- 当使用圆角,是对当前进行裁剪,圆角区域外的其他绘制不在显示,因此使用次组件,需要先绘制圆角区域以外的内容,最后绘制圆角和圆角内的部分;
作为返回总目录的传送门,请您访问此链接:前往总目录
全部评论 (0)
还没有任何评论哟~
