ホームページ > WeChat アプレット > ミニプログラム開発 > ミニ プログラムに画像コンポーネントを保存する機能を実装する手順を説明します。

ミニ プログラムに画像コンポーネントを保存する機能を実装する手順を説明します。

青灯夜游
リリース: 2021-10-27 10:46:40
転載
2441 人が閲覧しました

この記事では、WeChat アプレット 保存画像コンポーネントの開発について説明します。皆さんのお役に立てれば幸いです。

ミニ プログラムに画像コンポーネントを保存する機能を実装する手順を説明します。

多くの WeChat ミニ プログラムでは、ユーザーがポスターを保存することでアクティビティを共有し、より多くの人にミニ プログラムについて知らせることができます。私はミニ プログラムを開発しているときにこれに遭遇したはずです。バー。 [関連する学習の推奨事項: 小さなプログラム開発チュートリアル ]

今日は、会社で作成した小さなプログラムでポスターを保存する機能を共有します。まず、以前の社内のニーズがどのようなものだったのかをまず説明させていただきます。同社のオンライン ミニ プログラムは、新規ユーザーのプロモーションを長期的な目的としており、各ユーザーは独自のポスターを作成する必要があり、個人ポスターによるプロモーションは簡単な方法です。

私も課題を受けて、まずは調べ物をしようとユニバーサルインターネットに行ったのですが、先輩が「これと似たようなことをやったことがある」と教えてくれたんですが、その時は課題をこなすだけだったので、コードがとても汚かったので、彼は他のプロジェクトから始めました。私はコードを探して探して、ついに見つけました~~~しかし、私に与えられた時間は限られており、タスクは重いので、私が作らなければなりませんでしたいくつかの調整を行って最初に送信してください。その後、オンライン記事に従い、落とし穴を段階的に追跡し、ポスターを保存するためのコンポーネントを段階的に実装しました。

感想

まず、コンポーネントが uniapp を使用することを宣言しましょう。これは、具体的には、絵を描く、テキストを描く、ポスターをアルバムに保存するという基本的な機能を実装します。開発中に十分に使用されます。

キャンバスにポスターを描きます。 uni.canvasToTempFilePath を使用して、描画されたキャンバスを画像に変換します。 uni.saveImageToPhotosAlbum を使用して、携帯電話のアルバムへのローカル一時パスに写真を保存します。 私のアイデアは、使用されるすべてのメソッドをコンポーネントにカプセル化し、使用する必要があるメソッドを呼び出し、関連するパラメーターを調整するために親コンポーネントのみを使用することです。 具体的な使用方法については、サンプル コードをご覧ください。

キャンバスを介してポスター コンテンツを描画する順序

Promise を使用してポスター コンテンツを描画する順序を決定する物体。 promise.all() メソッドはキャンバス ペイント操作の最後のステップを実行します context.draw()

注 uni.getImageInfo()

  • 画像とアバターを描画するとき、コンポーネントは uni.getImageInfo() を通じて画像の関連情報を取得します。このメソッドの呼び出しが成功するための前提条件は、ダウンロード ドメイン名とリクエスト ドメインであることです。名前は WeChat アプレットのバックグラウンドで設定する必要がありますが、エラーを防ぐために、uploadFile ドメイン名も一緒に設定することが最善です。ただし、公式のヒントではダウンロードドメイン名のホワイトリストを設定することになっていますが、画像情報が取得できないという大きな落とし穴があります。

  • 関連する構成がない場合、デバッグ中または試用版、正式版などで vconsole デバッグ ツールが開きます。 uni.getImageInfo() は画像情報を取得できますが、vconsole を閉じると uni.getImageInfo() が失敗するという落とし穴もあります。

#このコンポーネント メソッド、変数の紹介

props

  • canvasInfo オブジェクト (必須)

      ##canvasWidth キャンバスの幅
    • canvasHeight キャンバスの高さ
    • canvasId キャンバスの識別
    ##isFullScreen Boolean
  • true の場合、キャンバスが携帯電話画面の全画面であることを意味し、canvasInfo で設定された幅と高さは無効になります。
    • デフォルトは false
    methods

canvasInit(callback)キャンバスの初期化、すべてのキャンバス操作はコールバック関数で実行する必要があります。
  • drawCanvasImage(context, src, _imageWidth, _imageHeight, dx, dy) Canvas に画像を描画します
  • drawCircularAvatar(context, url, _circularX) , _circularY, _circularR) キャンバス上に円形の絵を描きます
  • ##drawText(options) キャンバス上に単一行または複数行のテキストを描きます

  • ## startDrawToImage(context 、promiseArr、callback) Draw() on Canvas

  • posterToPhotosAlbum(filePath) 携帯電話のアルバムに保存

  • サンプルコード

    <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>
    ログイン後にコピー

効果

##プログラミング関連の知識の詳細については、

プログラミング入門

をご覧ください。 !

以上がミニ プログラムに画像コンポーネントを保存する機能を実装する手順を説明します。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:juejin.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート