本篇文章帶大家聊聊微信小程序保存圖片組件開發(fā),希望對(duì)大家有所幫助!
許多微信小程序通過保存海報(bào)讓用戶去分享活動(dòng)讓更多的人知道自己的小程序,想必在平時(shí)開發(fā)小程序的時(shí)候應(yīng)該有遇見過吧?!鞠嚓P(guān)學(xué)習(xí)推薦:小程序開發(fā)教程】
今天我就來分享下之前在公司做的一個(gè)小程序保存海報(bào)的功能。首先我先描述下之前在公司做的需求是什么樣的。公司上線的小程序會(huì)有一個(gè)長期的活動(dòng)目的就是去推廣新用戶,每個(gè)用戶都要有一張屬于自己的海報(bào),通過個(gè)人海報(bào)去推廣則只是單純的一種方式。
接到任務(wù)后,我也先去萬能互聯(lián)網(wǎng)做了調(diào)查但是我的師兄和我說這個(gè)做過類似的但是當(dāng)時(shí)只是單純?yōu)榱送瓿扇蝿?wù)所以代碼很亂,然后他就從其他項(xiàng)目的代碼找呀找,然后找到了給我~~~ 而當(dāng)時(shí)給到我的時(shí)間緊任務(wù)重呀只好先用著調(diào)整一些并且交差了。之后呢我就根據(jù)網(wǎng)上的文章然后一步一步踩坑,一步一步走實(shí)現(xiàn)了一個(gè)保存海報(bào)的組件。
思路
首先聲明下組件采用的是uniapp,具體實(shí)現(xiàn)了可以繪制圖片、繪制文字以及保存海報(bào)至相冊(cè)的基本功能,在開發(fā)中這些也完全夠用了。
通過canvas繪制海報(bào)。通過uni.canvasToTempFilePath
?將繪制好的 canvas轉(zhuǎn)為圖片。通過uni.saveImageToPhotosAlbum
?將本地臨時(shí)路徑的圖片保存至手機(jī)相冊(cè)中。而我的想法是將所有采用的方法全部封裝到組件中,只通過父組件去調(diào)用需要使用的方法和調(diào)整相關(guān)的參數(shù)即可。 具體使用可以查看示例代碼
通過canvas繪制海報(bào)內(nèi)容的順序先后問題
通過使用promise對(duì)象決定繪制海報(bào)內(nèi)容的順序先后。promise.all()
方法進(jìn)行canvas最后一步的繪畫操作?context.draw()
注意uni.getImageInfo()
在繪制圖片 和 頭像時(shí),組件通過
uni.getImageInfo()
?去獲取圖片的相關(guān)信息,調(diào)用該方法成功的前提是需要在微信小程序后臺(tái)配置download域名和request域名當(dāng)然最好把uploadFile域名也一起配置,防止出差錯(cuò)。但是官方給出的提示是配置download域名白名單即可,但是獲取不到圖片信息,這算是一個(gè)大坑了。如果沒有進(jìn)行相關(guān)配置,在調(diào)試時(shí) 或者 體驗(yàn)版 正式版等 打開了vconsole調(diào)試工具。uni.getImageInfo() 是可以獲取到圖片信息的,一旦關(guān)閉了vconsole uni.getImageInfo() 將會(huì)fail, 也是個(gè)坑。
本組件方法,變量介紹
props
-
canvasInfo Object (必需)
canvasWidth 畫布寬度
canvasHeight 畫布高度
canvasId 畫布標(biāo)識(shí)
-
isFullScreen Boolean
為ture時(shí)表示畫布為手機(jī)屏幕全屏,canvasInfo設(shè)置的寬高將失效。
默認(rèn)為 false
methods
canvasInit(callback) canvas初始化,所有有關(guān)畫布canvas操作需在其回調(diào)函數(shù)操作。
drawCanvasImage(context, src, _imageWidth, _imageHeight, dx, dy) 在canvas繪制一張圖片
drawCircularAvatar(context, url, _circularX, _circularY, _circularR) 在canvas繪制一張圓形圖片
drawText(options) 在canvas繪制單行、多行文本
startDrawToImage(context, promiseArr, callback) 將canvas操作draw()進(jìn)行繪制
posterToPhotosAlbum(filePath) 保存至手機(jī)相冊(cè)
示例代碼
<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">保存海報(bào)</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 // 繪制海報(bào)背景圖 let promise_1 = drawCanvasImage(context, this.canvasBgUrl, canvasWH.canvasWidth, canvasWH.canvasHeight) // 必須先繪制玩海報(bào)背景圖 再去操作其他繪制內(nèi)容 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: '保存海報(bào)', content: '海報(bào)將被保存至相冊(cè)中', 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畫布是不是全屏,默認(rèn)是false。 false時(shí)使用必須傳 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: { /** * 獲取用戶手機(jī)屏幕信息 */ getPhoneSystemInfo() { const res = uni.getSystemInfoSync(); return { windowWidth: res.windowWidth, windowHeight: res.windowHeight } }, /** 獲取 CanvasContext實(shí)例 * @param {String} canvasId */ getCanvasContextInit(canvasId) { return uni.createCanvasContext(canvasId, this) }, /** 保存海報(bào)組件初始化 * @param {Function} callback(context) 回調(diào)函數(shù) */ canvasInit(callback) { let context = this.getCanvasContextInit(this.canvasInfo.canvasId) if (context) { callback({ context: context, comThis: this }) } }, /** 將上訴的繪制畫到畫布中 并且 將畫布導(dǎo)出為圖片 * @param context 畫布 * @param {Promise[]} 存放Promise的數(shù)組 * @param {Function} callback 保存圖片后執(zhí)行的回調(diào)函數(shù)(本地圖片臨時(shí)路徑) */ 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) }, }) }) }, /** 繪制一個(gè)圓形頭像 * @param context 畫布 * @param url 圖片地址 * @param _circularX 圓心X坐標(biāo) * @param _circularY 圓心Y坐標(biāo) * @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坐標(biāo) _circularY圓的y坐標(biāo) _circularR圓的半徑 context.arc(_circularX, _circularY, _circularR, 0, 2 * Math.PI) context.clip() // dx: 圖像的左上角在目標(biāo)canvas上 X 軸的位置 // dy: 圖像的左上角在目標(biāo)canvas上 Y 軸的位置 // dwidth: 在目標(biāo)畫布上繪制圖像的寬度,允許對(duì)繪制的圖像進(jìn)行縮放 // dheight: 在目標(biāo)畫布上繪制圖像的高度,允許對(duì)繪制的圖像進(jìn)行縮放 context.drawImage(res.tempFilePath, dx, dy, dwidth, dheight) context.restore() // context.draw() resolve(context) } }) }) }, /** 繪制多行文本 注:, 和 空格都算一個(gè)字 * @param context 畫布 * @param text 需要被繪制的文本 * @param dx 左上角x坐標(biāo) * @param dy 右上角y坐標(biāo) * @param rowStrnum 每行多少個(gè)字 (默認(rèn)為text字體個(gè)數(shù)->單行) * @param fontSize 文字大小 (默認(rèn)16) * @param fontColor 文字顏色 (默認(rèn)black) * @param lineHeight 單行文本行高 (默認(rèn)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) // 獲取文本的字?jǐn)?shù) let textNum = text.length // 獲取行數(shù) 向上取整 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) }) }, /** 將畫布導(dǎo)出為圖片 * @param canvasId 畫布標(biāo)識(shí) */ canvasToImage(canvasId) { return new Promise((resolve, reject) => { uni.canvasToTempFilePath({ canvasId: canvasId, // 畫布標(biāo)識(shí) success: res => { // 在H5平臺(tái)下,tempFilePath 為 base64 resolve(res.tempFilePath) }, fail: err => { console.log('err', err) reject(err) } }, this) }) }, /** 保存生成的圖片到本地相冊(cè)中 * @param {String} filePath 圖片臨時(shí)路勁 */ posterToPhotosAlbum(filePath) { console.log('filePath',filePath) uni.showLoading({ title: '保存中...' }) uni.saveImageToPhotosAlbum({ filePath: filePath, success: (res) => { uni.showToast({ title: '保存成功,請(qǐng)前往手機(jī)相冊(cè)中查看', 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')) { // 用戶在保存圖片時(shí) 取消了 uni.showToast({ title: '已取消保存,無法保存至相冊(cè)', mask: true, icon: 'none', duration: 2000 }) return } }, complete: () => { uni.hideLoading() } }) }, /** * 打開攝像頭設(shè)置權(quán)限頁面 */ openSetting() { uni.showModal({ title: '溫馨提示', content: '保存圖片至相冊(cè)中,需要您同意添加訪問相冊(cè)權(quán)限', cancelText: '拒絕', confirmText: '同意', success: res => { if (res.confirm) { uni.openSetting({ success: settingdata => { if (settingdata.authSetting['scope.writePhotosAlbum']) { console.log('獲取權(quán)限成功,給出再次點(diǎn)擊圖片保存到相冊(cè)的提示。') uni.showToast({ title: '授權(quán)成功,請(qǐng)?jiān)俅吸c(diǎn)擊保存', icon: 'none', duration: 2000, }) } else { console.log('獲取權(quán)限失敗,給出不給權(quán)限就無法正常使用的提示') uni.showToast({ title: '需要訪問相冊(cè)權(quán)限', icon: 'none', duration: 2000, }) } }, fail: (res) => { console.log('err', err) } }) } else { uni.showToast({ title: '已拒絕授權(quán),無法保存至相冊(cè)', mask: true, icon: 'none', duration: 2000 }) return } } }) } } } </script> <style> </style>
效果
更多編程相關(guān)知識(shí),請(qǐng)?jiān)L問:編程入門??!
以上是手把手帶你在小程序中實(shí)現(xiàn)保存圖片組件功能的詳細(xì)內(nèi)容。更多信息請(qǐng)關(guān)注PHP中文網(wǎng)其他相關(guān)文章!

熱AI工具

Undress AI Tool
免費(fèi)脫衣服圖片

Undresser.AI Undress
人工智能驅(qū)動(dòng)的應(yīng)用程序,用于創(chuàng)建逼真的裸體照片

AI Clothes Remover
用于從照片中去除衣服的在線人工智能工具。

Clothoff.io
AI脫衣機(jī)

Video Face Swap
使用我們完全免費(fèi)的人工智能換臉工具輕松在任何視頻中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費(fèi)的代碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
功能強(qiáng)大的PHP集成開發(fā)環(huán)境

Dreamweaver CS6
視覺化網(wǎng)頁開發(fā)工具

SublimeText3 Mac版
神級(jí)代碼編輯軟件(SublimeText3)

閑魚官方微信小程序悄然上線,在小程序中可以發(fā)布閑置與買家/賣家私信交流、查看個(gè)人資料及訂單、搜索物品等,有用好奇閑魚微信小程序叫什么,現(xiàn)在快來看一下。閑魚微信小程序叫什么答案:閑魚,閑置交易二手買賣估價(jià)回收。1、在小程序中可以發(fā)布閑置、與買家/賣家私信交流、查看個(gè)人資料及訂單、搜索指定物品等功能;2、在小程序的頁面中有首頁、附近、發(fā)閑置、消息、我的5項(xiàng)功能;3、想要使用的話必要要開通微信支付才可以購買;

實(shí)現(xiàn)微信小程序中的圖片濾鏡效果隨著社交媒體應(yīng)用的流行,人們?cè)絹碓较矚g在照片中應(yīng)用濾鏡效果,以增強(qiáng)照片的藝術(shù)效果和吸引力。在微信小程序中也可以實(shí)現(xiàn)圖片濾鏡效果,為用戶提供更多有趣和創(chuàng)造性的照片編輯功能。本文將介紹如何在微信小程序中實(shí)現(xiàn)圖片濾鏡效果,并提供具體的代碼示例。首先,我們需要在微信小程序中使用canvas組件來加載和編輯圖片。canvas組件可以在頁面

實(shí)現(xiàn)微信小程序中的下拉菜單效果,需要具體代碼示例隨著移動(dòng)互聯(lián)網(wǎng)的普及,微信小程序成為了互聯(lián)網(wǎng)開發(fā)的重要一環(huán),越來越多的人開始關(guān)注和使用微信小程序。微信小程序的開發(fā)相比傳統(tǒng)的APP開發(fā)更加簡(jiǎn)便快捷,但也需要掌握一定的開發(fā)技巧。在微信小程序的開發(fā)中,下拉菜單是一個(gè)常見的UI組件,實(shí)現(xiàn)了更好的用戶操作體驗(yàn)。本文將詳細(xì)介紹如何在微信小程序中實(shí)現(xiàn)下拉菜單效果,并提供具

閑魚官方微信小程序已經(jīng)悄然上線,它為用戶提供了一個(gè)便捷的平臺(tái),讓你可以輕松地發(fā)布和交易閑置物品。在小程序中,你可以與買家或賣家進(jìn)行私信交流,查看個(gè)人資料和訂單,以及搜索你想要的物品。那么閑魚在微信小程序中究竟叫什么呢,這篇教程攻略將為您詳細(xì)介紹,想要了解的用戶們快來跟著本文繼續(xù)閱讀吧!閑魚微信小程序叫什么答案:閑魚,閑置交易二手買賣估價(jià)回收。1、在小程序中可以發(fā)布閑置、與買家/賣家私信交流、查看個(gè)人資料及訂單、搜索指定物品等功能;2、在小程序的頁面中有首頁、附近、發(fā)閑置、消息、我的5項(xiàng)功能;3、

微信小程序?qū)崿F(xiàn)圖片上傳功能隨著移動(dòng)互聯(lián)網(wǎng)的發(fā)展,微信小程序已經(jīng)成為了人們生活中不可或缺的一部分。微信小程序不僅提供了豐富的應(yīng)用場(chǎng)景,還支持開發(fā)者自定義功能,其中包括圖片上傳功能。本文將介紹如何在微信小程序中實(shí)現(xiàn)圖片上傳功能,并提供具體的代碼示例。一、前期準(zhǔn)備工作在開始編寫代碼之前,我們需要先下載并安裝微信開發(fā)者工具,并注冊(cè)成為微信開發(fā)者。同時(shí),還需要了解微信

實(shí)現(xiàn)微信小程序中的圖片旋轉(zhuǎn)效果,需要具體代碼示例微信小程序是一種輕量級(jí)的應(yīng)用程序,為用戶提供了豐富的功能和良好的用戶體驗(yàn)。在小程序中,開發(fā)者可以利用各種組件和API來實(shí)現(xiàn)各種效果。其中,圖片旋轉(zhuǎn)效果是一種常見的動(dòng)畫效果,可以為小程序增添趣味性和視覺效果。在微信小程序中實(shí)現(xiàn)圖片旋轉(zhuǎn)效果,需要使用小程序提供的動(dòng)畫API。下面是一個(gè)具體的代碼示例,展示了如何在小程

使用微信小程序?qū)崿F(xiàn)輪播圖切換效果微信小程序是一種輕量級(jí)的應(yīng)用程序,具有簡(jiǎn)單、高效的開發(fā)和使用特點(diǎn)。在微信小程序中,實(shí)現(xiàn)輪播圖切換效果是常見的需求。本文將介紹如何使用微信小程序?qū)崿F(xiàn)輪播圖切換效果,并給出具體的代碼示例。首先,在微信小程序的頁面文件中,添加一個(gè)輪播圖組件。例如,可以使用<swiper>標(biāo)簽來實(shí)現(xiàn)輪播圖的切換效果。在該組件中,可以通過b

實(shí)現(xiàn)微信小程序中的滑動(dòng)刪除功能,需要具體代碼示例隨著微信小程序的流行,開發(fā)者們?cè)陂_發(fā)過程中經(jīng)常會(huì)遇到一些常見功能的實(shí)現(xiàn)問題。其中,滑動(dòng)刪除功能是一個(gè)常見、常用的功能需求。本文將為大家詳細(xì)介紹如何在微信小程序中實(shí)現(xiàn)滑動(dòng)刪除功能,并給出具體的代碼示例。一、需求分析在微信小程序中,滑動(dòng)刪除功能的實(shí)現(xiàn)涉及到以下要點(diǎn):列表展示:要顯示可滑動(dòng)刪除的列表,每個(gè)列表項(xiàng)需要包
