>  기사  >  위챗 애플릿  >  미니 프로그램에서 그림 구성 요소 저장 기능을 구현하는 방법을 단계별로 안내해 드립니다.

미니 프로그램에서 그림 구성 요소 저장 기능을 구현하는 방법을 단계별로 안내해 드립니다.

青灯夜游
青灯夜游앞으로
2021-10-27 10:46:402396검색

이 기사에서는 WeChat 애플릿그림 저장 구성 요소 개발에 대해 이야기하겠습니다. 모든 사람에게 도움이 되기를 바랍니다.

미니 프로그램에서 그림 구성 요소 저장 기능을 구현하는 방법을 단계별로 안내해 드립니다.

많은 WeChat 미니 프로그램에서는 더 많은 사람들에게 미니 프로그램을 알리기 위해 포스터를 저장하여 활동을 공유할 수 있습니다. 미니 프로그램을 개발할 때 이런 현상을 접한 적이 있을 것입니다. [관련 학습 추천 : 미니 프로그램 개발 튜토리얼]

오늘은 회사에서 만든 작은 프로그램에 포스터를 저장하는 기능을 공유해보겠습니다. 먼저, 이전 회사에서는 어떤 요구사항이 있었는지 먼저 설명하겠습니다. 회사의 온라인 미니 프로그램은 신규 사용자를 홍보하는 장기적인 목적을 가지고 있습니다. 각 사용자는 자신만의 포스터를 가지고 있어야 하며 개인 포스터를 통한 홍보는 간단한 방법입니다.

과제를 받고 먼저 유니버셜 인터넷에 검색을 하러 갔는데, 형이 비슷한 일을 했다고 하더군요. 그냥 과제를 끝내기 위한 것이었기 때문에 코드가 너무 지저분해서 나중에는 다른 프로젝트의 코드에서 찾아보다가 찾았네요~~ 하지만 주어진 시간도 빡빡하고 업무도 무거워서 좀 수정해서 먼저 제출하게 되었습니다. 그 후 온라인 기사를 따라가며 함정을 단계별로 따라가며 포스터를 저장하는 구성요소를 단계별로 구현했습니다.

Idea

우선, 그림 그리기, 텍스트 그리기, 포스터 앨범 저장 등의 기본 기능을 구체적으로 구현한 uniapp을 사용한다는 점을 말씀드립니다.

캔버스를 통해 포스터를 그립니다. 그려진 캔버스를 그림으로 변환하려면 uni.canvasToTempFilePath를 사용하세요. 휴대폰 앨범의 로컬 임시 경로에 사진을 저장하려면 uni.saveImageToPhotosAlbum을 사용하세요. 내 생각은 사용되는 모든 메소드를 구성요소에 캡슐화하고 상위 구성요소만 사용하여 사용해야 하는 메소드를 호출하고 관련 매개변수를 조정하는 것입니다. 구체적인 사용법은 샘플 코드를 확인하세요uni.canvasToTempFilePath 将绘制好的 canvas转为图片。通过uni.saveImageToPhotosAlbum 将本地临时路径的图片保存至手机相册中。而我的想法是将所有采用的方法全部封装到组件中,只通过父组件去调用需要使用的方法和调整相关的参数即可。 具体使用可以查看示例代码

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

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

注意uni.getImageInfo()

  • 在绘制图片 和 头像时,组件通过uni.getImageInfo()

    포스터 콘텐츠를 캔버스에 그리는 순서

  • 그리는 순서 결정 Promise 객체를 이용하여 포스터 내용을 순서대로 작성합니다. promise.all() 메서드는 캔버스 그리기 작업 context.draw()
  • Note uni의 마지막 단계를 수행합니다. getImageInfo( )

그림과 아바타를 그릴 때 컴포넌트는 uni.getImageInfo()를 통해 그림의 관련 정보를 얻습니다. 이 메서드를 성공적으로 호출하기 위한 전제 조건은 다운로드 도메인 이름과 다운로드 도메인 이름은 WeChat 애플릿 백그라운드에서 구성해야 합니다. 물론 오류를 방지하려면 uploadFile 도메인 이름과 함께 요청 도메인 이름을 구성하는 것이 가장 좋습니다. 하지만 공식적인 팁은 다운로드 도메인 이름 화이트리스트를 구성했지만 이미지 정보를 얻을 수 없다는 것이 큰 함정입니다.

해당 구성이 없으면 디버깅이나 평가판, 정식 버전 등을 수행하는 동안 vconsole 디버깅 도구를 엽니다. uni.getImageInfo()는 이미지 정보를 얻을 수 있습니다. vconsole이 닫히면 uni.getImageInfo()가 실패하며 이는 함정이기도 합니다.
  • 이 컴포넌트 메소드, 변수 소개
    • props

    • canvasInfo 객체(필수)

    • canvasWidth 캔버스 너비

    canvasHeight 캔버스 높이
  • can vasI d 캔버스 식별자
    • isFullScreen Boolean

    이 true인 경우 캔버스가 휴대폰 화면의 전체 화면이며, canvasInfo에서 설정한 너비와 높이가 유효하지 않음을 의미합니다.

기본값은 false입니다
  • methods

  • canvasInit(콜백) 캔버스 초기화, 모든 캔버스 작업은 콜백 함수에서 수행되어야 합니다.

  • drawCanvasImage(context, src, _imageWidth, _imageHeight, dx, dy) 캔버스에 이미지 그리기

  • drawCircularAvatar(context, url, _circularX, _circularY, _circularR) 캔버스에 원형 이미지 그리기

  • drawText (옵션) 캔버스에 한 줄 또는 여러 줄의 텍스트 그리기

startDrawToImage(context, promiseArr, callback) 캔버스에 draw()를 실행하여 그림을 그립니다

posterToPhotosAlbum(filePath) 휴대폰 앨범에 저장

미니 프로그램에서 그림 구성 요소 저장 기능을 구현하는 방법을 단계별로 안내해 드립니다. 샘플 code

<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 &#39;@/components/SavePosterCom/SavePosterCom.vue&#39;
	export default {
		components: {
			SavePosterCom
		},
		data() {
			return {
				canvasInfo: {
					canvasWidth: 620,
					canvasHeight: 950,
					canvasId: &#39;save-poster&#39;
				},
				tempFilePath: &#39;&#39;,
				canvasBgUrl: &#39;https://images.pexels.com/photos/4065617/pexels-photo-4065617.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500&#39;,
				avatarUrl: &#39;https://p9-passport.byteacctimg.com/img/user-avatar/4dbf31fa6dec9c65b78a70d28d843c04~300x300.image&#39;
			}
		},
		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: &#39;皮皮虾仁&#39;,
						dx: (canvasWH.canvasWidth / 2) + 60,
						dy: canvasWH.canvasHeight / 4,
						fontSize: 30,
						fontColor: &#39;#5D4037&#39;
					})
					
					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: &#39;保存海报&#39;,
					content: &#39;海报将被保存至相册中&#39;,
					confirmText: &#39;保存&#39;,
					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 + &#39;px&#39;, height: canvasWH.canvasHeight + &#39;px&#39;}"></canvas>
	</view>
</template>

<script>
	export default {
		name: &#39;savePosterCom&#39;,
		data() {
			return {
				userPhoneWHInfo: {},
				canvasWH: {
					canvasWidth: 0,
					canvasHeight: 0
				}
			}
		},
		props: {
			// 决定保存下来的图片的宽高
			canvasInfo: {
				type: Object,
				default: () => {
					return {
						canvasWidth: 0,
						canvasHeight: 0,
						canvasId: &#39;canvasId&#39;
					}
				}
			},
			// 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 = &#39;&#39;
				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 = &#39;black&#39;
				} = options
				return new Promise((resolve, reject) => {
					context.setFontSize(fontSize)
					context.setFillStyle(fontColor)
					context.setTextBaseline(&#39;middle&#39;)
					// 获取需要绘制的文本宽度
					let textWidth = Number(context.measureText(text).width)
					// console.log(&#39;textWidth&#39;,textWidth)
					// 获取文本的字数 
					let textNum = text.length
					// 获取行数 向上取整
					let lineNum = Math.ceil(textNum / rowStrnum)
					// console.log(&#39;textNum&#39;,textNum)
					// console.log(&#39;lineNum&#39;,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(&#39;err&#39;, err)
							reject(err)
						}
					}, this)
				})
			},
			/** 保存生成的图片到本地相册中
			*  @param {String} filePath 图片临时路劲
			*/
			posterToPhotosAlbum(filePath) {
				console.log(&#39;filePath&#39;,filePath)
				uni.showLoading({
					title: &#39;保存中...&#39;
				})
				uni.saveImageToPhotosAlbum({
					filePath: filePath,
					success: (res) => {
						uni.showToast({
							title: &#39;保存成功,请前往手机相册中查看&#39;,
							mask: true,
							icon: &#39;none&#39;,
							duration: 2000
						})
					},
					fail: (err) => {
						console.log(&#39;err&#39;,err)
						if (err.errMsg.includes(&#39;deny&#39;)||err.errMsg.includes(&#39;denied&#39;)) { // 用户选择拒绝 
							this.openSetting()
						} else if (err.errMsg.includes(&#39;fail cancel&#39;)) { // 用户在保存图片时 取消了
							uni.showToast({
								title: &#39;已取消保存,无法保存至相册&#39;,
								mask: true,
								icon: &#39;none&#39;,
								duration: 2000
							})
							return
						}
					},
					complete: () => {
						uni.hideLoading()
					}
				})
			},
			/**
			* 打开摄像头设置权限页面
			*/
			openSetting() {
				uni.showModal({
					title: &#39;温馨提示&#39;,
					content: &#39;保存图片至相册中,需要您同意添加访问相册权限&#39;,
					cancelText: &#39;拒绝&#39;,
					confirmText: &#39;同意&#39;,
					success: res => {
						if (res.confirm) {
							uni.openSetting({
								success: settingdata => {
									if (settingdata.authSetting[&#39;scope.writePhotosAlbum&#39;]) {
										console.log(&#39;获取权限成功,给出再次点击图片保存到相册的提示。&#39;)
										uni.showToast({
											title: &#39;授权成功,请再次点击保存&#39;,
											icon: &#39;none&#39;,
											duration: 2000,
										})
									} else {
										console.log(&#39;获取权限失败,给出不给权限就无法正常使用的提示&#39;)
										uni.showToast({
											title: &#39;需要访问相册权限&#39;,
											icon: &#39;none&#39;,
											duration: 2000,
										})
									}
								},
								fail: (res) => {
									console.log(&#39;err&#39;, err)
								}
							})
						} else {
							uni.showToast({
								title: &#39;已拒绝授权,无法保存至相册&#39;,
								mask: true,
								icon: &#39;none&#39;,
								duration: 2000
							})
							return
						}
					}
				})
			}
		}
	}
</script>

<style>
</style>
Effect

🎜🎜🎜🎜더 많은 프로그래밍 관련 지식을 보려면 🎜프로그래밍 입문🎜을 방문하세요! ! 🎜

위 내용은 미니 프로그램에서 그림 구성 요소 저장 기능을 구현하는 방법을 단계별로 안내해 드립니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.cn에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제