html
<canvas type="2d" id="cardCanvas" canvas-id="cardCanvas"></canvas>
最好不要写在组件里,因为获取不到cardCanvas对象,渲染不了,以后再补充这个问题
ps:(最新class有更改,使用模板参照prototype方法里的判断自行设置…)
2022.5.25更新 :1.加入阴影(渐变制作)把画圆角路径分开成函数 2.再次运行anginDraw绘画任务时判断是否完成上次绘画,提供两种逻辑处理方式
class
附链接: 微信小程序canvas class源码以及使用数据等下载
使用
直接对类实例化
new canvasDr({ id: "#cardCanvas", data: inforData }).drawing();
其中data的数据格式大概是这样
let data = [
//头像
{
value: '',
type: 'img',
props: 'heard',
order: 0,
size: [140, 10, 150, 163],
style: {
radius: 65 / 2,
border: {
radius: 65 / 2,
color: "#15499C",
lineWidth: 5,
}
},
//头像阴影渐变层
{
type: 'gradual',
order: 1,
size: [50, 30, 75, 75],
style: {
size: [80, 70, 15, 80, 70, 50],
color: {
0: '#CFCFCF',
1: 'white'
}
}
},
{
//背景图
value: "",
type: 'img',
props: 'back',
order: 1,
size: [10, 10, 170, 163]
},
{
type: 'text',
order: 3,
props: 'name',
value: '',
style: {
color: '#000',
fontFrist: 'normal 600 ',
fontSize: 16,
fontLast: 'px PingFang SC'
},
size: [20, 55]
},
{
type: 'text',
order: 3,
props: 'post',
value: '',
style: {
color: '#15499C',
fontFrist: 'normal 400 ',
fontSize: 14,
fontLast: 'px PingFang SC'
},
size: [95, 55]
},
{
type: 'text',
order: 3,
props: 'tel',
value: '',
style: {
color: '#333333',
fontFrist: 'normal 400 ',
fontSize: 14,
fontLast: 'px PingFang SC'
},
size: [20, 78]
},
{
type: 'text',
order: 4,
props: 'companyName',
value: '',
style: {
color: '#333333',
fontFrist: 'normal 400 ',
fontSize: 14,
fontLast: 'px PingFang SC',
maxLetter: 18
},
size: [30, 134]
},
{
type: 'text',
order: 5,
props: 'companyAddress',
value: '',
style: {
color: '#333333',
fontFrist: 'normal 400 ',
fontSize: 12,
fontLast: 'px PingFang SC',
maxLetter: 18
},
size: [30, 154]
},
],
class源码
class canvasDr {
constructor(val) {
this.id = val.id;
this.data = this.deepClone(val.data);
this.canvas = '';
this.cxt = '';
this.dpr = '';
this.begin();
this.x = '';
this.y = '';
this.back = val.backFn;
this.needSaveImg = val.saveImg || false;
this.onDraw = false;
//进行比例转换,以300*240为基准,因为小程序分享最大尺寸是这个
this.cx = function (x) {
return ((x / 300 * this.x) * this.dpr)
}
this.cy = function (y) {
return ((y / 240 * this.y) * this.dpr)
}
this.notReady = true;
this.readyTime = 0;
return this;
}
drawing() {
this.onDraw = true;
console.log(this.onDraw, '开始绘画')
// 判断canvas是否初始化完成
if (this.notReady || !this.data) {
this.readyTime++
if (this.readyTime == 10) {
wx.showToast({
title: "图片渲染失败",
icon: "none",
});
return
} else {
setTimeout(() => {
this.drawing()
}, 200);
return
}
}
//背景填充
this.ctx.fillStyle = "#E6E6E6";
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
let data = this.data;
let leng = data.length;
//冒泡排序
for (let i = 0; i < leng; i++) {
for (let m = i; m < leng - 1; m++) {
if (data[m].order > data[m + 1].order) {
[data[m], data[m + 1]] = [data[m + 1], data[m]]
}
}
}
let list = data;
//递归
let k = 0,
i = 0;
console.log(list)
let doitImg = () => {
//强制停止当前渲染列表
if (this.over) {
this.over = false;
this.onDraw = false;
// 重新启动新的绘画
console.log('已经停止渲染')
this.drawing();
return
}
//递归防错误
i++;
if (i > 100) {
return
}
if (list[k]) {
console.log(k)
// console.log(list[k].props)
// console.log(list[k])
let methods = '',
mData = [];
if (list[k].type == 'img') {
// 图片
methods = 'useImg';
mData = [list[k].value, list[k].style, list[k].size]
} else if (list[k].type == 'rect') {
// 圆角矩形
methods = 'drawRoundedRect';
mData = [list[k].style, list[k].size]
} else if (list[k].type == 'text') {
// 文字
methods = 'drawText';
mData = [list[k].value, list[k].style, list[k].size]
} else if (list[k].type == 'fill') {
// 填充
methods = 'fillRect';
mData = [list[k].style, list[k].size]
} else if (list[k].type == 'gradual') {
// 渐变
methods = 'gradualChange';
mData = [list[k].style, list[k].size]
}
if (methods == '') {
wx.showToast({
title: '数据格式错误',
icon: 'none'
})
return
}
this[methods](...mData).then(() => {
k++
// setTimeout(() => {
doitImg()
// }, 500);
})
} else {
this.onDraw = false;
this.saveImg(this.back)
console.log(this.onDraw, '结束绘画')
}
}
doitImg()
}
begin() {
wx.createSelectorQuery()
.select('#' + this.id)
.fields({
node: true,
size: true,
})
.exec(this.init.bind(this));
//检查canvas的dom是否可正常读取到
// .exec((res) => {
// console.log(res, "seerererwr");
// });
}
init(res) {
//初始化canvas 设置像素为实际尺寸的倍数,增加清晰度
this.dpr = wx.getSystemInfoSync().pixelRatio;
this.x = res[0].width;
this.y = res[0].height;
this.canvas = res[0].node;
this.ctx = this.canvas.getContext("2d");
this.canvas.width = res[0].width * this.dpr;
this.canvas.height = res[0].height * this.dpr;
//定位
// this.rectangle([0, 1, 300, 239], "red");
this.notReady = false;
}
fillRect(style, size) {
return new Promise((res, rej) => {
size = this.doData(size);
this.ctx.fillStyle = style.color;
style = this.doData(style)
//剪切无效,换成画矩形
// if (style.radius) {
// style.radius = this.doData(style.radius)
// //开始路径,剪切处理
// this.ctx.save();
// var width = size[2];
// var height = size[3];
// var x = size[0],
// y = size[1],
// radius = style.radius;
// //开始路径,剪切处理
// this.ctx.save();
// this.drawRoundedLine(width,
// height,
// x,
// y,
// radius) //圆角路线
// this.ctx.clip(); //剪切路径
// //恢复状态
// this.ctx.restore();
// }
this.ctx.fillRect(...size);
res();
});
}
//绘画文字
drawText(title, val, size) {
return new Promise((res, rej) => {
//手动加省略号
if (val.maxLetter && title.length >= val.maxLetter) {
title = title.split("").splice(0, val.maxLetter).join("") + "..."
}
size = this.doData(size)
val = this.doData(val)
let font = (val.fontFrist ? val.fontFrist : '') +
val.fontSize +
(val.fontLast ? val.fontLast : "px Arial");
this.ctx.font = font;
//位置样式
if (val.textAlign) {
this.ctx.textAlign = val.textAlign;
}
this.ctx.fillStyle = val.color;
this.ctx.fillText(title, ...size);
res();
});
}
// 生成有圆角的矩形
drawRoundedRect(val, size, dotBig = '') {
if (!dotBig) {
size = this.doData(size)
val = this.doData(val)
}
let x = size[0],
y = size[1],
width = size[2],
height = size[3],
radius = val.radius,
color = val.color,
lineWidth = val.lineWidth;
return new Promise((res, rej) => {
this.ctx.strokeStyle = color;
this.ctx.lineWidth = lineWidth;
this.drawRoundedLine(width,
height,
x,
y,
radius) //圆角路线
this.ctx.closePath();
if (val.fill) {
this.ctx.fillStyle = val.fill;
this.ctx.fill();
}
this.ctx.stroke();
res();
});
}
//画矩形
rectangle(data, color) {
this.ctx.strokeStyle = color;
this.ctx.lineWidth = "4";
data = this.doData(data)
this.ctx.rect(...data);
this.ctx.stroke();
}
//话图片
useImg(url, style, data) {
return new Promise((res, rej) => {
var image = this.canvas.createImage();
//适应偏移图片
// 根据字段长度动态设置格式: data[n]['style'] = {
// widths: d.name.length,
// fontSize: 11,//单个文字长度
// maxLetter: 18//最大长度范围
// };
if (style) {
if (style.widths && style.maxLetter && style.fontSize) {
let num = style.widths > style.maxLetter ?
style.maxLetter / 2 :
style.widths / 2;
data[0] = data[0] - (num * style.fontSize);
console.log(data, "sdfsd")
}
}
data = this.doData(data)
image.src = url;
image.onload = () => {
if (style) {
if (style.radius) {
style = this.doData(style)
var width = data[2];
var height = data[3];
var x = data[0],
y = data[1];
var radius = style.radius;
//开始路径,剪切处理
this.ctx.save();
this.drawRoundedLine(width,
height,
x,
y,
radius) //圆角路线
this.ctx.clip(); //剪切路径
this.ctx.drawImage(image, ...data);
//恢复状态
this.ctx.restore();
}
if (style.border) {
//加边框
style.border = this.doData(style.border)
this.drawRoundedRect(style.border,
// [data[0] - r, data[1] - r, data[2] + r, data[3] + r])
data, 'dotBig')
}
} else {
this.ctx.drawImage(image, ...data);
}
res();
};
});
}
// 阴影 渐变
gradualChange(style, size) {
return new Promise((res, rej) => {
size = this.doData(size)
style.size = this.doData(style.size)
let grd = this.ctx.createRadialGradient(...style.size);
for (let k in style.color) {
grd.addColorStop(k, style.color[k]);
}
this.ctx.fillStyle = grd;
this.ctx.fillRect(...size);
res();
});
}
//处理传入数据
doData(data) {
for (let k in data) {
if (typeof data[k] != 'number' || k == 'maxLetter') {
continue
}
if (k % 2 == 0) {
data[k] = this.cx(data[k]);
} else {
data[k] = this.cy(data[k]);
}
}
return data
}
//画圆角路线为其他画图服务
drawRoundedLine(width,
height,
x,
y,
radius) {
this.ctx.beginPath();
this.ctx.arc(x + radius, y + radius, radius, Math.PI, (Math.PI * 3) / 2);
this.ctx.lineTo(width - radius + x, y);
this.ctx.arc(
width - radius + x,
radius + y,
radius,
(Math.PI * 3) / 2,
Math.PI * 2
);
this.ctx.lineTo(width + x, height + y - radius);
this.ctx.arc(
width - radius + x,
height - radius + y,
radius,
0,
(Math.PI * 1) / 2
);
this.ctx.lineTo(radius + x, height + y);
this.ctx.arc(
radius + x,
height - radius + y,
radius,
(Math.PI * 1) / 2,
Math.PI
);
}
//再次绘画
anginDraw(val) {
this.id = val.id;
this.data = this.deepClone(val.data);
this.canvas = '';
this.cxt = '';
this.dpr = '';
this.begin();
this.x = '';
this.y = '';
this.notReady = true;
this.readyTime = 0;
//两种解决重新绘画会造成上一个绘画任务继续在跑的问题
// 1.判断状态让用户点过 //需要判断上次继续绘画操作
// if (this.onDraw) {
// wx.showToast({
// title: '正在渲染请重试',
// icon: 'none'
// })
// } else {
// this.drawing();
// }
// 2.判断状态 停止当前渲染动作 //需要判断上次继续绘画操作
// this.over = false; //强行停止当前渲染
if (this.onDraw) {
// 停止当前渲染
this.over = true;
// console.log('正在停止渲染')
} else {
this.drawing();
}
}
deepClone(obj) {
if (typeof obj != 'object') return obj;
return JSON.parse(JSON.stringify(obj))
}
//储存为图片
saveImg(fn) {
wx.canvasToTempFilePath({
x: 0,
y: 0,
width: 300,
height: 500,
canvas: this.canvas,
canvasId: this.id,
success: function (res) {
// console.log(res.tempFilePath, "seerererwr");
// that.shareImg = res.tempFilePath;
// wx.setStorageSync("shareImg", res.tempFilePath);
if (fn) {
fn(res, 1)
}
if (this.needSaveImg) {
wx.saveImageToPhotosAlbum({
filePath: res.tempFilePath, //canvasToTempFilePath返回的tempFilePath
success: (ress) => {
// console.log(res, "seerererwr");
if (fn) {
fn(ress, 2)
}
},
fail: (err) => {
if (fn) {
fn(err, 3)
}
},
});
}
},
});
}
}
export default canvasDr