怎么在小程序中实现保存图片组件功能

发布时间:2021-10-27 11:04:09 作者:iii
来源:亿速云 阅读:214

这篇文章主要讲解了“怎么在小程序中实现保存图片组件功能”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么在小程序中实现保存图片组件功能”吧!

思路

首先声明下组件采用的是uniapp,具体实现了可以绘制图片、绘制文字以及保存海报至相册的基本功能,在开发中这些也完全够用了。

通过canvas绘制海报。通过uni.canvasToTempFilePath 将绘制好的 canvas转为图片。通过uni.saveImageToPhotosAlbum 将本地临时路径的图片保存至手机相册中。而我的想法是将所有采用的方法全部封装到组件中,只通过父组件去调用需要使用的方法和调整相关的参数即可。 具体使用可以查看示例代码

通过canvas绘制海报内容的顺序先后问题

通过使用promise对象决定绘制海报内容的顺序先后。promise.all()方法进行canvas最后一步的绘画操作 context.draw()

注意uni.getImageInfo()

本组件方法,变量介绍

props

methods

示例代码

<template>
	<view>
		<view class="savePosterItem">
			<image v-show="tempFilePath" :src="tempFilePath"></image>
			<save-poster-com v-show="!tempFilePath" ref="savePoster" :canvasInfo="canvasInfo"></save-poster-com>
		</view>
		
		
		<button class="savePosterBtn" type="primary" @click="saveBtnFun">保存海报</button>
	</view>
</template>

<script>
	import SavePosterCom from '@/components/SavePosterCom/SavePosterCom.vue'
	export default {
		components: {
			SavePosterCom
		},
		data() {
			return {
				canvasInfo: {
					canvasWidth: 620,
					canvasHeight: 950,
					canvasId: 'save-poster'
				},
				tempFilePath: '',
				canvasBgUrl: 'https://images.pexels.com/photos/4065617/pexels-photo-4065617.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500',
				avatarUrl: 'https://p9-passport.byteacctimg.com/img/user-avatar/4dbf31fa6dec9c65b78a70d28d843c04~300x300.image'
			}
		},
		onLoad() {
			let {
				drawCanvasImage,
				drawCircularAvatar,
				drawText
			} = this.$refs.savePoster.$options.methods
			this.$refs.savePoster.canvasInit(({
				context,
				comThis
			}) => {
				// 获取画布宽高
				let canvasWH = comThis.canvasWH
				// 绘制海报背景图
				let promise_1 = drawCanvasImage(context, this.canvasBgUrl, canvasWH.canvasWidth, canvasWH.canvasHeight)
				// 必须先绘制玩海报背景图 再去操作其他绘制内容
				promise_1.then(res => {
					let promise_2 = drawCircularAvatar(context, this.avatarUrl, canvasWH.canvasWidth / 2, canvasWH.canvasHeight /
						7, 70)
					
					let promise_3 = drawText({
						context: context,
						text: '皮皮虾仁',
						dx: (canvasWH.canvasWidth / 2) + 60,
						dy: canvasWH.canvasHeight / 4,
						fontSize: 30,
						fontColor: '#5D4037'
					})
					
					let promise_4 = drawCanvasImage(context, this.avatarUrl, 150, 150, (canvasWH.canvasWidth / 2) + 85, (canvasWH.canvasHeight -
						165))
					 
					this.$refs.savePoster.startDrawToImage(context, [promise_1,promise_2,promise_4], (tempFilePath) => {
						this.tempFilePath = tempFilePath
					})
				})
			})
		},
		methods: {
			saveBtnFun() {
				uni.showModal({
					title: '保存海报',
					content: '海报将被保存至相册中',
					confirmText: '保存',
					success: (res) => {
						if(res.confirm) {
							this.$refs.savePoster.posterToPhotosAlbum(this.tempFilePath)
						}
					}
				})
			}
		}
	}
</script>

<style>
	.savePosterItem {
		text-align: center;
	}
	.savePosterItem > image {
		width: 620rpx;
		height: 950rpx;
	}
	
	.savePosterBtn {
		margin-top: 40rpx;
		width: 80%;
	}
</style>

组件源码

<template>
	<view>
		<canvas :canvas-id="canvasInfo.canvasId" :style="{width: canvasWH.canvasWidth + 'px', height: canvasWH.canvasHeight + 'px'}"></canvas>
	</view>
</template>

<script>
	export default {
		name: 'savePosterCom',
		data() {
			return {
				userPhoneWHInfo: {},
				canvasWH: {
					canvasWidth: 0,
					canvasHeight: 0
				}
			}
		},
		props: {
			// 决定保存下来的图片的宽高
			canvasInfo: {
				type: Object,
				default: () => {
					return {
						canvasWidth: 0,
						canvasHeight: 0,
						canvasId: 'canvasId'
					}
				}
			},
			// canvas画布是不是全屏,默认是false。 false时使用必须传 canvasInfo
			isFullScreen: Boolean
		},
		created() {
			this.userPhoneWHInfo = this.getPhoneSystemInfo()
			if (this.isFullScreen) { // 画布全屏
				this.canvasWH.canvasWidth = this.userPhoneWHInfo.windowWidth
				this.canvasWH.canvasHeight = this.userPhoneWHInfo.windowHeight
			} else { // 指定宽高
				this.canvasWH.canvasWidth = this.canvasInfo.canvasWidth
				this.canvasWH.canvasHeight = this.canvasInfo.canvasHeight
			}
		},
		mounted() {},
		methods: {
			/**
			* 获取用户手机屏幕信息
			*/
			getPhoneSystemInfo() {
				const res = uni.getSystemInfoSync();
				return {
					windowWidth: res.windowWidth,
					windowHeight: res.windowHeight
				}
			},
			/** 获取 CanvasContext实例
			* @param {String} canvasId 
			*/
			getCanvasContextInit(canvasId) {
				return uni.createCanvasContext(canvasId, this)
			},
			/** 保存海报组件初始化
			* @param {Function} callback(context) 回调函数
			*/
			canvasInit(callback) {
				let context = this.getCanvasContextInit(this.canvasInfo.canvasId)
				if (context) {
					callback({
						context: context,
						comThis: this
					})
				}
			},
			/** 将上诉的绘制画到画布中 并且 将画布导出为图片
			*  @param context 画布
			*  @param {Promise[]} 存放Promise的数组 
			*  @param {Function} callback 保存图片后执行的回调函数(本地图片临时路径)
			*/
			startDrawToImage(context, promiseArr, callback) {
				// 将之前在绘图上下文中的描述(路径、变形、样式)画到 canvas 中
				let canvasId = this.canvasInfo.canvasId
				let tempFilePath = ''
				Promise.all(promiseArr).then(res => {
					context.draw(false, async () => {
						callback(await this.canvasToImage(canvasId))
					})
				})
			},
			/**
			* 在canvas绘制一张图片
			* @param context 画布
			* @param src 图片资源
			* @param _imageWidth 图片宽度
			* @param _imageHeight 图片高度 
			*/
			drawCanvasImage(context, src, _imageWidth, _imageHeight, dx, dy) {
				return new Promise((resolve, reject) => {
					uni.getImageInfo({
						src: src,
						success: res => {
							context.drawImage(res.path, (dx - _imageWidth), (dy - _imageHeight), _imageWidth, _imageHeight)
							resolve(context)
						},
					})
				})
			},
			/** 绘制一个圆形头像
			* @param  context 画布 
			* @param  url     图片地址
			* @param  _circularX  圆心X坐标
			* @param  _circularY  圆心Y坐标
			* @param  _circularR  圆半径
			*/
			drawCircularAvatar(context, url, _circularX, _circularY, _circularR) {
				let dx = _circularX - _circularR;
				let dy = _circularY - _circularR;
				let dwidth = _circularR * 2;
				let dheight = _circularR * 2
				return new Promise((resolve, reject) => {
					uni.downloadFile({
						url: url,
						success: res => {
							context.save()
							context.beginPath()
							// _circularX圆的x坐标  _circularY圆的y坐标  _circularR圆的半径
							context.arc(_circularX, _circularY, _circularR, 0, 2 * Math.PI)
							context.clip()
							// dx: 图像的左上角在目标canvas上 X 轴的位置
							// dy: 图像的左上角在目标canvas上 Y 轴的位置
							// dwidth: 在目标画布上绘制图像的宽度,允许对绘制的图像进行缩放
							// dheight: 在目标画布上绘制图像的高度,允许对绘制的图像进行缩放
							context.drawImage(res.tempFilePath, dx, dy, dwidth, dheight)
							context.restore()
							// context.draw()
							resolve(context)
						}
					})
				})
			},
			/** 绘制多行文本 注:, 和 空格都算一个字
			* @param context 画布
			* @param text 需要被绘制的文本
			* @param dx 左上角x坐标
			* @param dy 右上角y坐标
			* @param rowStrnum 每行多少个字 (默认为text字体个数->单行)
			* @param fontSize 文字大小 (默认16)
			* @param fontColor 文字颜色 (默认black)
			* @param lineHeight 单行文本行高 (默认0)
			*/
			drawText(options) {
				let {
					context,
					text,
					dx,
					dy,
					rowStrnum = text.length,
					lineHeight = 0,
					fontSize = 16,
					fontColor = 'black'
				} = options
				return new Promise((resolve, reject) => {
					context.setFontSize(fontSize)
					context.setFillStyle(fontColor)
					context.setTextBaseline('middle')
					// 获取需要绘制的文本宽度
					let textWidth = Number(context.measureText(text).width)
					// console.log('textWidth',textWidth)
					// 获取文本的字数 
					let textNum = text.length
					// 获取行数 向上取整
					let lineNum = Math.ceil(textNum / rowStrnum)
					// console.log('textNum',textNum)
					// console.log('lineNum',lineNum)
					for (let i = 0; i < lineNum; i++) {
						let sliceText = text.slice(i * rowStrnum, (i + 1) * rowStrnum)
						// fillText 的 dx = 文字最左边的距离到屏幕政策的距离
						context.fillText(sliceText, dx - textWidth, dy + i * lineHeight);
					}
					resolve(context)
				})
			},
			/** 将画布导出为图片
			* @param canvasId 画布标识
			*/
			canvasToImage(canvasId) {
				return new Promise((resolve, reject) => {
					uni.canvasToTempFilePath({
						canvasId: canvasId, // 画布标识
						success: res => {
							// 在H5平台下,tempFilePath 为 base64
							resolve(res.tempFilePath)
						},
						fail: err => {
							console.log('err', err)
							reject(err)
						}
					}, this)
				})
			},
			/** 保存生成的图片到本地相册中
			*  @param {String} filePath 图片临时路劲
			*/
			posterToPhotosAlbum(filePath) {
				console.log('filePath',filePath)
				uni.showLoading({
					title: '保存中...'
				})
				uni.saveImageToPhotosAlbum({
					filePath: filePath,
					success: (res) => {
						uni.showToast({
							title: '保存成功,请前往手机相册中查看',
							mask: true,
							icon: 'none',
							duration: 2000
						})
					},
					fail: (err) => {
						console.log('err',err)
						if (err.errMsg.includes('deny')||err.errMsg.includes('denied')) { // 用户选择拒绝 
							this.openSetting()
						} else if (err.errMsg.includes('fail cancel')) { // 用户在保存图片时 取消了
							uni.showToast({
								title: '已取消保存,无法保存至相册',
								mask: true,
								icon: 'none',
								duration: 2000
							})
							return
						}
					},
					complete: () => {
						uni.hideLoading()
					}
				})
			},
			/**
			* 打开摄像头设置权限页面
			*/
			openSetting() {
				uni.showModal({
					title: '温馨提示',
					content: '保存图片至相册中,需要您同意添加访问相册权限',
					cancelText: '拒绝',
					confirmText: '同意',
					success: res => {
						if (res.confirm) {
							uni.openSetting({
								success: settingdata => {
									if (settingdata.authSetting['scope.writePhotosAlbum']) {
										console.log('获取权限成功,给出再次点击图片保存到相册的提示。')
										uni.showToast({
											title: '授权成功,请再次点击保存',
											icon: 'none',
											duration: 2000,
										})
									} else {
										console.log('获取权限失败,给出不给权限就无法正常使用的提示')
										uni.showToast({
											title: '需要访问相册权限',
											icon: 'none',
											duration: 2000,
										})
									}
								},
								fail: (res) => {
									console.log('err', err)
								}
							})
						} else {
							uni.showToast({
								title: '已拒绝授权,无法保存至相册',
								mask: true,
								icon: 'none',
								duration: 2000
							})
							return
						}
					}
				})
			}
		}
	}
</script>

<style>
</style>

感谢各位的阅读,以上就是“怎么在小程序中实现保存图片组件功能”的内容了,经过本文的学习后,相信大家对怎么在小程序中实现保存图片组件功能这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!

推荐阅读:
  1. 功能完善的小程序日历组件的实现
  2. 小程序实现长按保存图片的方法

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

php

上一篇:java框架中spring aop两种配置方式分别是什么

下一篇:Mysql数据分组排名实现的示例分析

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》