Uniapp中canvas画图生成图片并下载到相册
注意点:
- 画图时,如果有数字的需要 +'',转为字符串,不然画不出来
let serviceTime = that.time ? that.time + '' : '10';
- 画图时绘制引入多张图片时,需要定义一个函数来创建和返回图片加载的Promise对象,不然图片有的会加载不出来,等待所有图片加载完成
return Promise.all(imagePromises).then
再绘制,使用wx.nextTick
确保绘制完成。 - 文字居中时,需要计算文字的宽度,然后减去一半,再绘制文字,不然文字会偏移。
1. 画图
javascript
<template>
<view class="container">
<!-- canvas画图生成的图片 -->
<image style="position: absolute;top: 0;left: 0;"
:style="{ width: canvasObj.w + 'rpx', height: canvasObj.h + 'rpx' }"
:src="downPic"
:show-menu-by-longpress="true">
</image>
<!-- canvas -->
<canvas
type="2d" id="canvas"
class="canvas" canvas-id="canvas"
:style="{ width: canvasObj.w + 'rpx', height: canvasObj.h + 'rpx' }">
</canvas>
<button @tap="drawCanvas">canvas绘制图片</button>
</view>
</template>
<script>
export default {
data() {
return {
downPic: "",
canvasObj: {
w: 750,
h: 1333,
},
};
},
methods: {
async drawCanvas() {
uni.showLoading({
title: 'canvas绘制图片中...'
});
let that = this;
await uni.createSelectorQuery()
.select('#canvas')
.fields({ node: true, size: true })
.exec((res) => {
//开始画图
console.log('获取到的canvas元素res', res)
that.canvas = res[0].node
that.canvas.width = that.canvasObj.w
that.canvas.height = that.canvasObj.h
that.ctx = that.canvas.getContext('2d')
that.ctx.clearRect(0, 0, that.canvas.width, that.canvas.height);
console.log('开始画图');
that.canvasDraw().then(res => {
console.log('渲染文字', res)
// 渲染文字
that.$nextTick(() => {
that.txt();
});
}).then(() => {
console.log('生成图片', res)
// canvas生成图片
that.$nextTick(() => {
that.canvasImg();
});
})
})
},
canvasDraw() {
let that = this;
// 创建一个数组来存储每个图片加载的Promise
const imagePromises = [];
// 定义一个函数来创建和返回图片加载的Promise
function loadImage(src) {
return new Promise((resolve, reject) => {
const img = that.canvas.createImage();
img.src = src + '?' + new Date().getTime();
img.onload = () => resolve(img);
img.onerror = (error) => reject(error);
});
}
// 加载所有需要的图片
imagePromises.push(loadImage(that.$util.img('/upload/zyz/annualReport/page-bg7.png')));
imagePromises.push(loadImage(that.$util.img('/upload/zyz/annualReport/logo7.png')));
imagePromises.push(loadImage(that.$util.img('/upload/zyz/annualReport/title7.png')));
imagePromises.push(loadImage(that.$util.img(`/upload/zyz/annualReport/${that.userAttendInfo.userLevel}.png`)));
imagePromises.push(loadImage(that.$util.img('/upload/zyz/annualReport/code.png')));
// 等待所有图片加载完成
return Promise.all(imagePromises).then(images => {
// 按照顺序绘制图片
that.ctx.drawImage(images[0], 0, 0, that.canvasObj.w, that.canvasObj.h); // bgImg
that.ctx.drawImage(images[2], 100, 173, 580, 271); // titleImg
that.ctx.drawImage(images[1], 33, 69, 336, 66); // logoImg
that.ctx.drawImage(images[3], 222, 750, 350, 378); // modelImg
// that.ctx.drawImage(images[4], 48, 1100, 184, 184);
let width = 220;
let height = 220;
let min = Math.min(width, height)
let circle = {
x: Math.floor(min / 2),
y: Math.floor(min / 2),
r: Math.floor(min / 2)
}
that.ctx.save();
that.ctx.beginPath();
//开始路径画圆,剪切处理
that.ctx.arc(150, 1180, circle.r, 0, Math.PI * 2, false);
that.ctx.clip()
that.ctx.drawImage(images[4], 40, 1070, 2 * circle.r, 2 * circle.r)
that.ctx.restore();
// 绘制白色描边
that.ctx.save();
that.ctx.beginPath();
that.ctx.arc(150, 1180, 114, 0, 2 * Math.PI, false);
that.ctx.strokeStyle = '#ffffff';
that.ctx.lineWidth = 10;
that.ctx.stroke();
that.ctx.restore();
that.ctx.draw();
// 使用 wx.nextTick 确保绘制完成
return new Promise(resolve => {
if (typeof wx !== 'undefined' && wx.nextTick) {
wx.nextTick(() => {
resolve(true);
});
} else {
// 如果不在微信小程序环境中,则回退到 requestAnimationFrame
requestAnimationFrame ? requestAnimationFrame(() => resolve(true)) : setTimeout(() => resolve(true), 0);
}
});
}).catch(error => {
console.error('Error loading images:', error);
return false; // 返回失败状态
});
},
letterspacing(content, x, y) {
console.log(content);
let that = this
let currentX = x;
for (let i = 0; i < content.length; i++) {
that.ctx.fillText(content[i], currentX, y);
currentX += that.ctx.measureText(content[i]).width + 2;
}
},
txt() {
// 设置文本字体、大小和样式
let that = this;
let x = 190;
let y = 480;
that.ctx.font = '26px youziFont';
that.ctx.fillStyle = 'black';
that.ctx.textAlign = 'center';
that.ctx.textBaseline = 'top';
// 第1行文本
that.ctx.fillStyle = '#F27C25';
let userName = `${that.userAttendInfo.userName}:`;
const userNameWidth = that.ctx.measureText(userName).width + 20;
that.letterspacing(userName,x , y)
// 第2行文本
that.ctx.fillStyle = 'black';
let content1 = "2024 年,";
// 计算整个文本的总宽度
const totalWidth2 = that.ctx.measureText(content1).width;
// 计算居中位置
const centerX2 = (that.canvasObj.w - totalWidth2) / 2;
// 重新绘制文本
that.letterspacing(content1, centerX2, y + 50);
// 第3行文本
that.ctx.fillStyle = 'black';
let content8 = "你以青春为名,赴志愿之旅。";
// 计算整个文本的总宽度
const totalWidth8 = that.ctx.measureText(content8).width;
// 计算居中位置
const centerX8 = (that.canvasObj.w - totalWidth8) / 2;
// 重新绘制文本
that.letterspacing(content8, centerX8, y + 50 * 2);
// 第4行文本
let content2 = "参与最多的是";
let serviceType = that.userAttendInfo.serviceName ? that.userAttendInfo.serviceName : '';
let content3 = ' 志愿服务,';
// 计算每个部分内容的宽度
const content2Width = that.ctx.measureText(content2).width + 20;
const serviceTypeWidth = that.ctx.measureText(serviceType).width + 20;
// 计算整个文本的总宽度
const totalWidth4 = that.ctx.measureText(content2).width + that.ctx.measureText(serviceType).width + that.ctx.measureText(content3).width;
// 计算居中位置
const centerX4 = (that.canvasObj.w - totalWidth4) / 2;
// 重新绘制文本
that.letterspacing(content2, centerX4, y + 50 * 3);
that.ctx.fillStyle = '#F27C25'; // 设置颜色为 #F27C25
that.letterspacing(serviceType, centerX4 + content2Width, y + 50 * 3);
that.ctx.fillStyle = 'black'; // 设置颜色为黑色
that.letterspacing(content3, centerX4 + content2Width + serviceTypeWidth, y + 50 * 3);
// 第5行文本
let content4 = "被授予";
let serviceTitle = `" ${that.userAttendInfo.serviceName}达人"`;
let content5 = '称号!';
const content4Width = that.ctx.measureText(content4).width + 4;
const serviceTitleWidth = that.ctx.measureText(serviceTitle).width + 20;
const totalWidth5 = that.ctx.measureText(content4).width + that.ctx.measureText(serviceTitle).width + that.ctx.measureText(content5).width;
// 计算居中位置
const centerX5 = (that.canvasObj.w - totalWidth5) / 2;
// 重新绘制文本
that.ctx.fillStyle = 'black';
that.letterspacing(content4, centerX5, y + 50 * 4);
that.ctx.fillStyle = '#F27C25'; // 设置颜色为 #F27C25
that.letterspacing(serviceTitle, centerX5 + content4Width, y + 50 * 4);
that.ctx.fillStyle = 'black';
that.letterspacing(content5, centerX5 + content4Width + serviceTitleWidth, y + 50 * 4);
// 第6行文本
that.ctx.fillStyle = 'black';
let content6 = "累计服务为";
let serviceTime = that.time ? that.time + '' : '10';
let content7 = '小时,荣获';
// 计算每个部分内容的宽度
const content6Width = that.ctx.measureText(content6).width + 4;
const serviceTimeWidth = that.ctx.measureText(serviceTime).width + 10;
// 计算整个文本的总宽度
const totalWidth7 = that.ctx.measureText(content6).width + that.ctx.measureText(serviceTime).width + that.ctx.measureText(content7).width;
// 计算居中位置
const centerX7 = (that.canvasObj.w - totalWidth7) / 2;
// 重新绘制文本
that.letterspacing(content6, centerX7, y + 50 * 5);
that.ctx.fillStyle = '#F27C25'; // 设置颜色为 #F27C25
that.letterspacing(serviceTime, centerX7 + content6Width, y + 50 * 5);
that.ctx.fillStyle = 'black'; // 设置颜色为黑色
that.letterspacing(content7, centerX7 + content6Width + serviceTimeWidth, y + 50 * 5);
},
canvasImg() {
//canvas生成图片
let that = this;
uni.canvasToTempFilePath({
canvas: that.canvas,
success: function (res) {
console.log('生成图片成功:')
console.log(res)
uni.hideLoading();
that.downPic = res.tempFilePath
setTimeout(function () {
that.showReport = true
},1000)
// that.saveImage()
},
fail: function (err) {
console.log('生成图片失败:')
console.log(err)
}
})
},
},
};
</script>
<style>
.container {
width: 100%;
height: 100%;
}
.canvas {
//display: none; 会导致ios下无法生成
//让画布的位置固定在屏幕之外
left: 9000px;
width: 1200px;
height: 1500px;
position: fixed;
}
</style>
2. 上面绘制好图片,获取用户相册权限,保存图片到手机相册
javascript
<template>
<view class="container">
<!-- canvas画图生成的图片 -->
<image style="position: absolute;top: 0;left: 0;"
:style="{ width: canvasObj.w + 'rpx', height: canvasObj.h + 'rpx' }"
:src="downPic"
:show-menu-by-longpress="true">
</image>
<!-- canvas -->
<canvas
type="2d" id="canvas"
class="canvas" canvas-id="canvas"
:style="{ width: canvasObj.w + 'rpx', height: canvasObj.h + 'rpx' }">
</canvas>
<button @click="saveImage">保存图片</button>
</view>
</template>
<script>
export default {
methods: {
saveImage() {
var _this = this;
uni.saveImageToPhotosAlbum({
filePath: _this.downPic,
success() {
uni.showToast({
title: "图片已保存图片到相册",
icon: 'none',
duration: 2000
})
},
fail() {
uni.hideLoading()
uni.showModal({
content: '检测到您没打开获取信息功能权限,是否去设置打开?',
confirmText: "确认",
cancelText: '取消',
success: (res) => {
if (res.confirm) {
uni.openSetting({
success: (res) => {
console.log(res);
uni.showToast({
title: "请重新点击保存图片~",
icon: "none"
});
}
})
} else {
uni.showToast({
title: "保存失败,请打开权限功能重试",
icon: "none"
});
}
}
})
}
})
},
},
},
};
</script>
<style>
.canvas-container {
width: 100%;
height: 100%;
}
</style>
3. 总结:
在这个例子中,我们首先创建了一个canvas上下文,然后使用drawCanvas
方法绘制了一个白色背景的矩形。接着,我们使用saveImage
方法将canvas内容转换为图片,并保存到相册中。最后,我们在页面上添加了一个按钮,点击按钮时调用saveImage
方法。