• 技术文章 >微信小程序 >小程序开发

    手把手带你在小程序中实现保存图片组件功能

    青灯夜游青灯夜游2021-10-27 10:46:40转载422
    本篇文章带大家聊聊微信小程序保存图片组件开发,希望对大家有所帮助!

    许多微信小程序通过保存海报让用户去分享活动让更多的人知道自己的小程序,想必在平时开发小程序的时候应该有遇见过吧。【相关学习推荐:小程序开发教程

    今天我就来分享下之前在公司做的一个小程序保存海报的功能。首先我先描述下之前在公司做的需求是什么样的。公司上线的小程序会有一个长期的活动目的就是去推广新用户,每个用户都要有一张属于自己的海报,通过个人海报去推广则只是单纯的一种方式。

    接到任务后,我也先去万能互联网做了调查但是我的师兄和我说这个做过类似的但是当时只是单纯为了完成任务所以代码很乱,然后他就从其他项目的代码找呀找,然后找到了给我~~~ 而当时给到我的时间紧任务重呀只好先用着调整一些并且交差了。之后呢我就根据网上的文章然后一步一步踩坑,一步一步走实现了一个保存海报的组件。

    思路

    首先声明下组件采用的是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>

    效果

    jgedx-x12pb.gif

    更多编程相关知识,请访问:编程入门!!

    以上就是手把手带你在小程序中实现保存图片组件功能的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:掘金社区,如有侵犯,请联系admin@php.cn删除
    上一篇:浅谈小程序中下拉刷新和上拉加载功能怎么实现?(附代码) 下一篇:浅谈小程序中怎么创建页面二维码
    Web大前端开发直播班

    相关文章推荐

    • 微信小程序中如何实现子向父传参(页面通信)• 手把手教你在小程序中怎么实现悬浮按钮(代码示例)• 小程序怎么配置Twoxml,让其完美支持Markdown!• 什么是思维导图?怎么使用F6在小程序中绘制思维导图?• 详解微信小程序中如何安装和引用ECharts?

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网