需求:有用户头像,用户昵称,活动标题,商品主图,商品标题,商品价格,小程序二维码
思路:写成一个js文件,就可以在需要的组件中混入使用(mixins),调用绘制函数:createCanvasImage('posterCanvas'),保存图片函数:saveCanvasImage。
方法:canvas的dom元素的top写很大,相当于隐藏。canvas画好后,调用getCanvasImage函数得到图片(posterImg)显示。
调用的组件dom代码:
注释:
js代码:
export default {
data() {
return {
posterObj: {
url: "", //商品主图
userImg: "", //用户头像
nickname: "", //用户昵称
dec: "", //介绍
title: "", //标题
discountPrice: "", //折后价格
orignPrice: "", //原价
code: "", //小程序码
},
canvasFlag: true, // 是否显示canvas
posterImg: "",//canvas绘制后得到的图片
x: 25, // 绘制起点x
y: 0, // 绘制起点y
w: 270, // 绘制宽度
h: 345, // 绘制高度
radius: 10, // 圆角
padding: 10, // 边距
imgW: 187.5, // 主图片宽
imgH: 187.5, //主图片高
imgBg: "#f7f7f7", //主图片背景色
};
},
methods: {
// 画圆角矩形 ctx、x起点、y起点、w宽度、h高度、r圆角半径、fillColor填充颜色、strokeColor边框颜色
roundRect(ctx, x, y, w, h, r, fillColor, strokeColor, btn) {
// 开始绘制
ctx.beginPath();
// 绘制左上角圆弧 Math.PI = 180度
// 圆心x起点、圆心y起点、半径、以3点钟方向顺时针旋转后确认的起始弧度、以3点钟方向顺时针旋转后确认的终止弧度
ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5);
// 绘制border-top
// 移动起点位置 x终点、y终点
ctx.moveTo(x + r, y);
// 画一条线 x终点、y终点
ctx.lineTo(x + w - r, y);
// 绘制右上角圆弧
ctx.arc(x + w - r, y + r, r, Math.PI * 1.5, Math.PI * 2);
// 绘制border-right
ctx.lineTo(x + w, y + h - r);
// 绘制右下角圆弧
ctx.arc(x + w - r, y + h - r, r, 0, Math.PI * 0.5);
// 绘制左下角圆弧
ctx.arc(x + r, y + h - r, r, Math.PI * 0.5, Math.PI);
// 绘制border-left
ctx.lineTo(x, y + r);
if (btn == "btn") {
const grd = ctx.createLinearGradient(0, 0, 200, 0); //渐变色
grd.addColorStop(0, fillColor);
grd.addColorStop(1, strokeColor);
// 因为边缘描边存在锯齿,最好指定使用 transparent 填充
ctx.setFillStyle(grd);
// 对绘画区域填充
ctx.fill();
} else {
if (fillColor) {
// 因为边缘描边存在锯齿,最好指定使用 transparent 填充
ctx.setFillStyle(fillColor);
// 对绘画区域填充
ctx.fill();
}
if (strokeColor) {
// 因为边缘描边存在锯齿,最好指定使用 transparent 填充
ctx.setStrokeStyle(strokeColor);
// 画出当前路径的边框
ctx.stroke();
}
}
// 关闭一个路径
ctx.closePath();
// 剪切,剪切之后的绘画绘制剪切区域内进行,需要save与restore 这个很重要 不然没办法保存
ctx.clip();
// console.log("画圆角");
},
/**
* canvas绘图相关,把文字转化成只能行数,多余显示省略号
* ctx: 当前的canvas
* text: 文本
* contentWidth: 文本最大宽度
* lineNumber: 显示几行
*/
canvasMultiLineText(ctx, text, contentWidth, lineNumber) {
var textArray = text.split(""); // 分割成字符串数组
var temp = "";
var row = [];
for (let i = 0; i < textArray.length; i++) {
if (ctx.measureText(temp).width < contentWidth) {
temp += textArray[i];
} else {
i--; // 这里添加i--是为了防止字符丢失
row.push(temp);
temp = "";
}
}
row.push(temp);
// 如果数组长度大于2,则截取前两个
if (row.length > lineNumber) {
var rowCut = row.slice(0, lineNumber);
var rowPart = rowCut[1];
var test = "";
var empty = [];
for (var a = 0; a < rowPart.length; a++) {
if (ctx.measureText(test).width < contentWidth) {
test += rowPart[a];
} else {
break;
}
}
empty.push(test); // 处理后面加省略号
var group = empty[0] + "...";
rowCut.splice(lineNumber - 1, 1, group);
row = rowCut;
}
return row;
},
// 绘制图片 : 当前canvas 图 x起点 y起点 w宽度 h高度 r圆角 c1填充颜色 c2边框颜色
drawImg(ctx, img, x, y, w, h, r, c1, c2) {
let _this = this;
//将网络图片转成本地路径
ctx.restore();
uni.getImageInfo({
src: img,
success(res) {
ctx.save();
//覆盖绘制
//问题:在微信小程序使用canvas绘制圆角图片时,微信调试工具正常显示,android真机都不显示。
// 原因:因为ctx.clip()剪切区域使用的填充颜色是透明的,所以图片没出来。
// 解决方案:将剪切区域设置成实体颜色就好了。
_this.roundRect(ctx, x, y, w, h, r, c1, c2); //绘制图片圆角背景
ctx.drawImage(res.path, x, y, w, h, r); //绘制图
ctx.restore(); //恢复之前保存的绘图上下文 恢复之前保存的绘图上下午即状态 可以继续绘制
ctx.draw(true);
},
fail() {
_this.canvasFlag = true;
uni.showToast({ title: "img生成失败", duration: 2000, icon: "none" });
},
});
},
// 绘制文本 : 当前canvas 文本 x起点 y起点 c填充颜色 s样式
drawText(ctx, text, x, y, c, s) {
ctx.setFillStyle(c);
ctx.font = s;
ctx.fillText(text, x, y);
ctx.draw(true);
},
// 绘制两行标题 : 当前canvas 文本 x起点 y起点 c填充颜色 s样式
drawTitle(ctx, text, x, y, c, s) {
ctx.setGlobalAlpha(1); //不透明
ctx.setFillStyle(c); //文字颜色:默认黑色
ctx.font = s;
let row = this.canvasMultiLineText(ctx, text, 180, 2); //计算绘制的2行文本
let leftSpace = x + 10; // 这段文字起始的X位置
let textLineHeight = 18; // 一行文字加一行行间距
for (let b = 0; b < row.length; b++) {
//一行一行绘制文本
ctx.fillText(row[b], leftSpace, y + textLineHeight * b - 15, 180);
ctx.draw(true);
}
},
// 绘制价格 : 当前canvas 现价 原价 x起点 y起点
drawPrice(ctx, zpPrice, orignPrice, x, y) {
ctx.setFillStyle("#FF354D"); //文字颜色:默认黑色
ctx.setFontSize(21); //设置字体大小,默认10
let zpPriceW = ctx.measureText(zpPrice).width; //文本的宽度
ctx.fillText(`${zpPrice}`, x, y + 30, zpPriceW);
ctx.beginPath(); //开始一个新的路径
ctx.setFontSize(14); //设置字体大小,默认10
ctx.setFillStyle("#999"); //文字颜色:默认黑色
let orignPriceW = ctx.measureText(orignPrice).width; //去掉市场价
ctx.fillText(`¥${orignPrice}`, x + zpPriceW, y + 30, orignPriceW); //5价格间距
ctx.moveTo(x + zpPriceW, y + 25); //设置线条的起始路径坐标
ctx.lineTo(x + zpPriceW + orignPriceW, y + 25); //设置线条的终点路径坐标
ctx.setStrokeStyle("#999");
ctx.stroke(); //对当前路径进行描边
ctx.closePath(); //关闭当前路径
},
// 生成海报
createCanvasImage(id) {
// console.log(this.posterObj, "posterObj");
uni.showLoading({
title: "海报生成中...",
});
let _this = this;
const ctx = uni.createCanvasContext(id);
ctx.draw(); //清空原来的画图内容
ctx.save();
this.roundRect(ctx, this.x, this.y, this.w, this.h, this.radius, "#fff", "#fff"); //绘制海报圆角背景白色的
ctx.restore(); //恢复之前保存的绘图上下文 恢复之前保存的绘图上下午即状态 可以继续绘制
ctx.save();
//将网络图片转成本地路径 用户头像
this.drawImg(
ctx,
this.posterObj.userImg,
this.x + this.padding,
this.y + this.padding,
50,
50,
25,
this.imgBg,
this.imgBg
);
// 用户昵称
this.drawText(ctx, this.posterObj.nickname, this.x + 70, this.y + 30, "#333", "normal bold 18px sans-serif");
// dec介绍
this.drawText(ctx, this.posterObj.dec, this.x + 70, this.y + 55, "#666", "normal 14px sans-serif");
//将网络图片转成本地路径 商品图片
this.drawImg(
ctx,
this.posterObj.url,
this.x + 41,
this.y + 70,
this.imgW,
this.imgH,
this.radius,
this.imgBg,
this.imgBg
);
// 海报商品title
let contentTextY = this.y + 295; // 这段文字起始的y位置
_this.drawTitle(ctx, this.posterObj.title, this.x, contentTextY, "#333", "normal bold 14px sans-serif");
//绘制价格
this.drawPrice(ctx, this.posterObj.discountPrice, this.posterObj.orignPrice, this.x + this.padding, contentTextY);
// 二维码 图标
this.drawImg(ctx, this.posterObj.code, this.x + 200, this.y + 271, 60, 60, 0, "#ffffff", this.imgBg);
setTimeout(() => {
this.getCanvasImage(id);
setTimeout(() => {
uni.hideLoading();
}, 300);
}, 300);
},
getCanvasImage(id) {
let _this = this;
// 把画布转化成临时文件
uni.canvasToTempFilePath({
x: _this.x,
y: _this.y,
quality: 1,
width: this.w,
height: this.h,
destWidth: this.w * 10,
destHeight: this.h * 10,
canvasId: id,
success(res) {
_this.posterImg = res.tempFilePath;
},
fail() {
uni.showToast({ title: "生成失败,稍后再试", duration: 2000, icon: "none" });
},
});
},
// 保存到系统相册
saveCanvasImage() {
uni.showLoading({
title: "保存中...",
});
let _this = this;
// 保存图片至相册
uni.saveImageToPhotosAlbum({
filePath: _this.posterImg,
success(res2) {
uni.hideLoading();
uni.showToast({ title: "图片保存成功,可以去分享啦~", icon: "none", duration: 2000 });
_this.canvasCancelEvn();
},
fail() {
uni.showToast({ title: "保存失败,稍后再试", duration: 2000, icon: "none" });
uni.hideLoading();
},
});
},
// 取消海报
canvasCancelEvn() {
// console.log();
this.$emit("cancel", true);
},
},
};