UniApp在微信小程序中的Canvas画布操作
-
画布建立
- 建立画布
<!-- 禁止画布被拖动 --> <canvas canvas-id="posterid" style="width:300rpx;height:400rpx;" disable-scroll="true" @touchstart="" @touchmove="" @touchend="" > </canvas>
- js声明画布对象
var ctx = uni.createCanvasContext('posterid') //····绘画操作 ctx.draw(); //绘制图像
-
网络图片在画布中需要先下载到本地才可以绘入画布中,最后导出图片
-
下载网络图片(这一步要先准备好)
uni.downloadFile({ url: imgUrl, success: (res) => { if (res.statusCode === 200) { uni.setStorageSync('headImg',res.tempFilePath); } },fail: (err) => { console.log(err); } })
-
先确认是否可以获取到网络图片下载到本地后的信息才可以开始绘画
-
获取图片信息
getImageInfo(image) { //返回一个Promise对象,包含用户的信息 return new Promise((req, rej) => { uni.getImageInfo({ src: image, success: function(res) { req(res) }, }); }) }
-
-
-
绘制的几个方法
-
文本换行
/** * @desc canvas文字过长换行脚本 * @param {Object} ctx canvas对象 * @param {String} text 文字 * @param {Number} x 距离左边的宽度 * @param {Number} y 距离右边的宽度 * @param {Number} w 文本区域的宽度 * @param {Object} fontStyle 文本的字体风格/位置,有默认值 */ ctb (ctx,text,x,y,w,fontStyle) { ctx.save(); ctx.fillStyle = fontStyle.fillStyle; ctx.textAlign = fontStyle.textAlign; ctx.textBaseline = fontStyle.textBaseline; const chr = text.split(''); const row = []; let temp = ''; /* 判断如果末尾是,!。》 就不要换行 判断如果末尾是《 就要换行 */ for (let a = 0; a < chr.length; a++) { if (ctx.measureText(temp).width < w) { } else { if (/[,。!》]/im.test(chr[a])) { // console.log(`我是${chr[a]},我在末尾,我不换行`); temp += ` ${chr[a]}`; // 跳过这个字符 a++; } if (/[《]/im.test(chr[a - 1])) { // console.log(`我是${chr[a-1]},我在末尾,我要换行`); // 删除这个字符 temp = temp.substr(0, temp.length - 1); a--; } row.push(temp); temp = ''; } temp += chr[a] ? chr[a] : ''; } row.push(temp); for (let b = 0; b < row.length; b++) { ctx.fillText(row[b], x, y + b * fontStyle.lineHeight); } }
-
矩形图片裁剪成原型
/** * @param {Object} ctx 创建的画布对象 * @param {Object} width 矩形的宽 * @param {Object} height 矩形的高 * @param {Object} x 矩形左上角x轴坐标点 * @param {Object} y 矩形左上角y轴坐标点 * @param {Object} url 绘制的图片的URL */ drawCircular(ctx,width, height, x, y, url) { var avatarurl_width = width; var avatarurl_heigth = height; var avatarurl_x = x; var avatarurl_y = y; ctx.save(); ctx.beginPath(); ctx.arc(avatarurl_width / 2 + avatarurl_x, avatarurl_heigth / 2 + avatarurl_y, avatarurl_width / 2, 0, Math.PI * 2, false); ctx.clip(); ctx.drawImage(url, avatarurl_x, avatarurl_y, avatarurl_width, avatarurl_heigth); ctx.restore(); }
-
-
绘画需要异步执行
//海报绘制 async draw(){ var ctx = uni.createCanvasContext('posterid') //获取图片信息 let headImg = await this.getImageInfo(uni.getStorageSync("headImg")); //延迟绘制海报 setTimeout(() =>{ //绘画操作 //····················· ctx.draw(false,() =>{ //绘画成功的操作,可以存放保存图片 setTimeout(() =>{ uni.canvasToTempFilePath({ canvasId: 'posterid', destWidth: this.cropW * this.canvasScale * 5, //分享图片尺寸=画布尺寸1*缩放比0.5*像素比2 destHeight: this.cropH * this.canvasScale * 5, quality:1, fileType:'png', success: (res) => { if(res.errMsg == "canvasToTempFilePath:ok"){ uni.saveImageToPhotosAlbum({ filePath: res.tempFilePath, success:(res) => { uni.showToast({ icon: 'none', position: 'bottom', title: "图片已下载至【图库】,请打开【图库】查看", }); },fail: (err) => { //重新提示用户打开保存图片的授权 if(err.errMsg === "saveImageToPhotosAlbum:fail auth deny"){ uni.showModal({ title: '提示', content: '需要您授权保存相册', showCancel: false, success(res) { if (res.confirm) { uni.openSetting({ success(settingdata) { if (settingdata.authSetting['scope.writePhotosAlbum']) { uni.showModal({ title: '提示', content: '获取权限成功,再次保存图片即可成功', showCancel: false, }) } else { uni.showModal({ title: '提示', content: '获取权限失败,无法保存到相册', showCancel: false }) } } }) } } }) }else{ uni.showToast({ icon: 'none', title: '下载失败', }); } } }); } }, }, this); },500) }) },1500) }
---------------------------------------------------------------------------------------------------------新项目修正------------------------------
drawPageImg() {
const ctx = uni.createCanvasContext('myCanvas');
const ctxW = '750'; //canvas的宽度
const ctxH = '1762'; //canvas的高度
/**
* 文字绘制
* @param {Object} ctx
* @param {Object} text 内容
* @param {Object} x x轴坐标
* @param {Object} y y轴坐标
* @param {Object} w 文字占用的宽度面基
* @param {Object} size 字号
*/
const ctxText = (ctx, text, x, y, w, size, color) => {
var temp = '';
var row = [];
let textAll = text.split('');
for (var a = 0; a < textAll.length; a++) {
if (ctx.measureText(temp).width < w) {
} else {
row.push(temp);
temp = '';
}
temp += textAll[a];
}
row.push(temp);
ctx.font = `${size}px arail`;
ctx.textAlign = 'left';
ctx.lineHeight = '30px';
if(color) {
ctx.fillStyle = color;
} else {
ctx.fillStyle = '#333';
}
ctx.globalCompositeOperation = 'source-over';
for (var b = 0; b < row.length; b++) {
ctx.fillText(row[b] || '', x, y + (b + 1) * 20);
}
};
const ctxTextB = (ctx, text, x, y, w, size, color) => {
var temp = '';
var row = [];
let textAll = text.split('');
for (var a = 0; a < textAll.length; a++) {
if (ctx.measureText(temp).width < w) {
} else {
row.push(temp);
temp = '';
}
temp += textAll[a];
}
row.push(temp);
ctx.font = `${size}px arail`;
ctx.textAlign = 'left';
ctx.lineHeight = '30px';
if(color) {
ctx.fillStyle = color;
} else {
ctx.fillStyle = '#333';
}
ctx.globalCompositeOperation = 'source-over';
for (var b = 0; b < row.length; b++) {
ctx.fillText(row[b] || '', x, y + (b + 1) * 30);
}
};
/**
* 画一个带圆角矩形
* @param {Object} ctx
* @param {Object} img 图片
* @param {Object} x,
* @param {Object} y
* @param {Object} width 宽
* @param {Object} height 高
* @param {Object} r 圆角
* @param {Object} shadow 阴影
*/
const ctxCircular = (ctx, img, color, x, y, width, height, r, shadow) => {
ctx.beginPath(); //开始绘制
ctx.save(); //保存(canvas)状态
ctx.moveTo(x + r, y);
ctx.lineTo(x + width - r, y);
ctx.arc(x + width - r, y + r, r, Math.PI * 1.5, Math.PI * 2);
ctx.lineTo(x + width, y + height - r);
ctx.arc(x + width - r, y + height - r, r, 0, Math.PI * 0.5);
ctx.lineTo(x + r, y + height);
ctx.arc(x + r, y + height - r, r, Math.PI * 0.5, Math.PI);
ctx.lineTo(x, y + r);
ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5);
if (shadow == 1) {
ctx.shadowBlur = 20; // 模糊效果程度的
ctx.shadowColor = 'red'; // 阴影颜色
}
if (color) {
ctx.fillStyle = color;
} else {
ctx.fillStyle = 'rgba(0,0,0,0)'
}
ctx.fill(); //对当前路径中的内容进行填充
ctx.clip(); //从原始画布中剪切任意形状和尺寸
ctx.closePath(); //关闭一个路径
if(img) {
ctx.drawImage(img, x, y, width, height);
}
ctx.restore(); //恢复(canvas)状态
ctx.globalCompositeOperation = 'source-over';
};
// 开始绘制
ctx.drawImage(this.shareBg, 0, 0, ctxW, ctxW); // 绘制背景
ctxText(ctx, this.shopName, 30, 76, 528, 34, 0); // 绘制门店名称
ctxText(ctx, '减重排行榜', 30, 134, 150, 30, 0); // 绘制门店名称
ctxCircular(ctx, this.shareLogo, 0, 564, 76, 156, 156, 20, 0) // 绘制门店logo
ctxCircular(ctx, 0, '#fff', 30, 186, 380, 40, 10, 0) // 绘制日期背景颜色
ctxText(ctx, this.list.startDate + ' - ' + this.list.endDate, 60, 196, 360, 26); // 绘制日期
// 绘制排名背景
ctxCircular(ctx, this.oneZhu, 0, 265, 351, 220, 249, 0, 0);
ctxCircular(ctx, this.twoZhu, 0, 45, 381, 220, 249, 0, 0);
ctxCircular(ctx, this.threeZhu, 0, 485, 401, 220, 249, 0, 0);
// 绘制排名底座
ctxCircular(ctx, this.oneBg, 0, 328, 270, 113, 113, 0, 0);
ctxCircular(ctx, this.twoBg, 0, 108, 300, 113, 113, 0, 0);
ctxCircular(ctx, this.threeBg, 0, 548, 320, 113, 113, 0, 0);
// 绘制头像
console.log("this.rankList[0].txImg", this.rankList[0].txImg);
ctxCircular(ctx, this.rankList[0].txImg, 0, 330, 291, 90, 90, 45, 0);
ctxCircular(ctx, this.rankList[1].txImg, 0, 110, 321, 90, 90, 45, 0);
ctxCircular(ctx, this.rankList[2].txImg, 0, 550, 341, 90, 90, 45, 0);
// 绘制内容
// 第一
ctxText(ctx, this.rankList[0].studentName + '(' + this.rankList[0].trainer + ')', 315, 421, 164, 20, "#1C9098");
ctxText(ctx, '减重重量', 286, 469, 80, 20, "#1C9098");
ctxText(ctx, '减重比率', 390, 469, 80, 20, "#1C9098");
ctxText(ctx, this.rankList[0].weightLoss + '斤', 294, 502, 60, 20, "#1C9098");
ctxText(ctx, this.rankList[0].ration + '%', 403, 502, 60, 20, "#1C9098");
// 第二
ctxText(ctx, this.rankList[1].studentName + '(' + this.rankList[1].trainer + ')', 95, 451, 164, 20, "#097159");
ctxText(ctx, '减重重量', 60, 499, 80, 20, "#097159");
ctxText(ctx, '减重比率', 170, 499, 80, 20, "#097159");
ctxText(ctx, this.rankList[1].weightLoss + '斤', 74, 532, 60, 20, "#097159");
ctxText(ctx, this.rankList[1].ration + '%', 183, 532, 60, 20, "#097159");
// 第三
ctxText(ctx, this.rankList[2].studentName + '(' + this.rankList[2].trainer + ')', 535, 471, 164, 20, "#097159");
ctxText(ctx, '减重重量', 500 ,519, 80, 20, "#097159");
ctxText(ctx, '减重比率', 610, 519, 80, 20, "#097159");
ctxText(ctx, this.rankList[2].weightLoss + '斤', 514, 552, 60, 20, "#097159");
ctxText(ctx, this.rankList[2].ration + '%', 623, 552, 60, 20, "#097159");
// 表头
ctxCircular(ctx, 0, "#fff", 20, 598, 710, 93, 20, 0);
ctxText(ctx, '排名', 50, 632, 48, 24, "#8A8697"); // 绘制门店名称
ctxText(ctx, '学员(教练)', 185, 632, 144, 24, "#8A8697"); // 绘制门店名称
ctxText(ctx, '减重(斤)', 425, 632, 88, 24, "#8A8697"); // 绘制门店名称
ctxText(ctx, '减重比率', 604, 632, 96, 24, "#8A8697"); // 绘制门店名称
// 排名
for(let i = 0; i < 7;i++) {
ctxCircular(ctx, 0, "#fff", 20, 701 + (112 * i), 710, 102, 20, 0);
ctxText(ctx, JSON.stringify(4 + i), 65, 746 + (112 * i), 38, 32, "#151B2A");
ctxCircular(ctx, this.rankList[3 + i].txImg, 0, 144, 721 + (112 * i), 62, 62, 31, 0);
ctxText(ctx, this.rankList[3 + i].studentName + '(' + this.rankList[3 + i].trainer + ')', 226, 742 + (112 * i), 210, 24, "#151B2A");
ctxText(ctx, JSON.stringify(this.rankList[3 + i].weightLoss), 455, 742 + (112 * i), 164, 24, "#FF4763");
ctxText(ctx, this.rankList[3 + i].ration + '%', 615, 742 + (112 * i), 60, 24, "#10D63B");
}
// 底部
ctxCircular(ctx, 0, "#fff", 20, 1495, 710, 174, 20, 0);
ctxCircular(ctx, this.shareMa, 0, 60, 1515, 134, 134, 0, 0);
ctxText(ctx, '183-5833-2800', 228, 1539, 300, 44, "#151B2A");
ctxTextB(ctx, '地址:浙江省嘉兴市海宁市皮都锦江酒店158号', 228, 1569, 850, 24, "#151B2A");
ctxText(ctx, '中健keep提供数据支持', 252, 1699, 246, 24, "#4D8F4D");
ctx.draw(
false,
(() => {
setTimeout(() => {
// 将canvas 变成图片方便发送给好友或者保存
uni.canvasToTempFilePath({
canvasId: 'myCanvas',
destWidth: ctxW * 5, //展示图片尺寸=画布尺寸1*像素比2
destHeight: ctxH * 5,
quality: 1,
fileType: 'png',
success: (res) => {
uni.hideLoading();
uni.hideLoading();
console.log(res.tempFilePath);
this.canvasImg = res.tempFilePath;
this.isImgBox = true;
},
fail: function (error) {
uni.hideLoading();
uni.showToast({
icon: 'none',
position: 'bottom',
title: '绘制图片失败'
});
}
});
}, 100);
})()
);
},