需求:有用户头像,用户昵称,活动标题,商品主图,商品标题,商品价格,小程序二维码
思路:写成一个js文件,就可以在需要的组件中混入使用(mixins),调用绘制函数:createCanvasImage('posterCanvas'),保存图片函数:saveCanvasImage。
方法:canvas的dom元素的top写很大,相当于隐藏。canvas画好后,调用getCanvasImage函数得到图片(posterImg)显示。
调用的组件dom代码:<canvas class="canvas" canvas-id="posterCanvas"></canvas> <image :src="posterImg" class="img" />
注释:<!-- canvas 要放在调用的组件里(外面),不能放在封装组件里面 会找不到 canvas-->
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); }, }, };