Uniapp中canvas画图生成图片并下载到相册

Uniapp中canvas画图生成图片并下载到相册

注意点:

  1. 画图时,如果有数字的需要 +'',转为字符串,不然画不出来let serviceTime = that.time ? that.time + '' : '10';
  2. 画图时绘制引入多张图片时,需要定义一个函数来创建和返回图片加载的Promise对象,不然图片有的会加载不出来,等待所有图片加载完成return Promise.all(imagePromises).then 再绘制,使用 wx.nextTick 确保绘制完成。
  3. 文字居中时,需要计算文字的宽度,然后减去一半,再绘制文字,不然文字会偏移。

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方法。

4.最终效果,类似下图(有些不方便没有展示,整个项目过程都已经实现),有文字、背景、颜色(这些不太重要,重要是多张图片不漏画):

相关推荐
niew~4 小时前
用uni-app写的开灯、关灯的面板界面
前端·javascript·uni-app
寰梦5 小时前
Docker(Nginx) 部署 uniapp
nginx·docker·uni-app
一朵好运莲9 小时前
HBuilderX(uni-app)Vue3路由传参和接收路由参数!!
前端·vue.js·uni-app
哟哟耶耶14 小时前
learn-(Uni-app)输入框u-search父子组件与input输入框(防抖与搜索触发)
uni-app
写BUG天花板14 小时前
Vscode 构建 uniapp vue3 + ts 微信小程序项目
vscode·微信小程序·uni-app
wangjiecsdn18 小时前
uniapp打包APP跳转华为应用商城(跳转各个应用商店)
华为·uni-app
fakaifa18 小时前
CRMEB Pro版v3.2源码全开源+PC端+Uniapp前端+搭建教程
小程序·uni-app·php·生活·开源软件·源码下载
不学习何以强国21 小时前
uni-app 个人课程表页面
前端·javascript·uni-app
三天不学习1 天前
uni-app H5端使用注意事项 【跨端开发系列】
uni-app
mySunshine9211 天前
uniapp改成用vue起项目
前端·vue.js·uni-app