小程序如何生成海報分享朋友圈
發表時(shí)間:2021-1-5
發布人(rén):融晨科技
浏覽次數:52
項目需求寫完有一段時(shí)間了(le/liǎo),但是(shì)還是(shì)想回過來(lái)總結一下,一是(shì)對項目的(de)回顧優化等,二是(shì)對坑的(de)地(dì / de)方做個(gè)記錄,避免以(yǐ)後遇到(dào)類似的(de)問題。
需求
利用微信強大(dà)的(de)社交能力通過小程序達到(dào)裂變的(de)目的(de),拉取新用戶。
生成的(de)海報如下
需求分析
1、利用小程序官方提供的(de)api可以(yǐ)直接分享轉發到(dào)微信群打開小程序
2、利用小程序生成海報保存圖片到(dào)相冊分享到(dào)朋友圈,用戶長按識别二維碼關注公衆号或者打開小程序來(lái)達到(dào)裂變的(de)目的(de)
實現方案
一、分析如何實現
相信大(dà)家應該都會有類似的(de)迷惑,就(jiù)是(shì)如何按照産品設計的(de)那樣繪制成海報,其實當時(shí)我也(yě)是(shì)不(bù)知道(dào)如何下手,認真想了(le/liǎo)下得通過canvas繪制成圖片,這(zhè)樣用戶保存這(zhè)個(gè)圖片到(dào)相冊,就(jiù)可以(yǐ)分享到(dào)朋友圈了(le/liǎo)。但是(shì)要(yào / yāo)繪制的(de)圖片上(shàng)面不(bù)僅有文字還有數字、圖片、二維碼等且都是(shì)活的(de),這(zhè)個(gè)要(yào / yāo)怎麽動态生成呢。認真想了(le/liǎo)下,需要(yào / yāo)一點一點的(de)将文字和(hé / huò)數字,背景圖繪制到(dào)畫布上(shàng)去,這(zhè)樣通過api最終合成一個(gè)圖片導出(chū)到(dào)手機相冊中。
二、需要(yào / yāo)解決的(de)問題
1、二維碼的(de)動态獲取和(hé / huò)繪制(包括如何生成小程序二維碼、公衆号二維碼、打開網頁二維碼)
2、背景圖如何繪制,獲取圖片信息
3、将繪制完成的(de)圖片保存到(dào)本地(dì / de)相冊
4、處理用戶是(shì)否取消授權保存到(dào)相冊
三、實現步驟
這(zhè)裏我具體寫下圍繞上(shàng)面所提出(chū)的(de)問題,描述大(dà)概實現的(de)過程
①首先創建canvas畫布,我把畫布定位設成負的(de),是(shì)爲(wéi / wèi)了(le/liǎo)不(bù)讓它顯示在(zài)頁面上(shàng),是(shì)因爲(wéi / wèi)我嘗試把canvas通過判斷條件動态的(de)顯示和(hé / huò)隐藏,在(zài)繪制的(de)時(shí)候會出(chū)現問題,所以(yǐ)采用了(le/liǎo)這(zhè)種方法,這(zhè)裏還有一定要(yào / yāo)設置畫布的(de)大(dà)小。
<canvas canvas-id="myCanvas" style="width: 690px;height:1085px;position: fixed;top: -10000px;"></canvas>
②創建好畫布之(zhī)後,先繪制背景圖,因爲(wéi / wèi)背景圖我是(shì)放在(zài)本地(dì / de),所以(yǐ)獲取 <canvas> 組件 canvas-id 屬性,通過createCanvasContext創建canvas的(de)繪圖上(shàng)下文 CanvasContext 對象。使用drawImage繪制圖像到(dào)畫布,第一個(gè)參數是(shì)圖片的(de)本地(dì / de)地(dì / de)址,後面兩個(gè)參數是(shì)圖像相對畫布左上(shàng)角位置的(de)x軸和(hé / huò)y軸,最後兩個(gè)參數是(shì)設置圖像的(de)寬高。
const ctx = wx.createCanvasContext('myCanvas')
ctx.drawImage('/img/study/shareimg.png', 0, 0, 690, 1085)
③創建好背景圖後,在(zài)背景圖上(shàng)繪制頭像,文字和(hé / huò)數字。通過getImageInfo獲取頭像的(de)信息,這(zhè)裏需要(yào / yāo)注意下在(zài)獲取的(de)網絡圖片要(yào / yāo)先配置download域名才能生效,具體在(zài)小程序後台設置裏配置。
獲取頭像地(dì / de)址,首先量取頭像在(zài)畫布中的(de)大(dà)小,和(hé / huò)x軸Y軸的(de)坐标,這(zhè)裏的(de)result[0]是(shì)我用promise封裝返回的(de)一個(gè)圖片地(dì / de)址
let headImg = new Promise(function (resolve) {
wx.getImageInfo({
src: `${app.globalData.baseUrl2}${that.data.currentChildren.headImg}`,
success: function (res) {
resolve(res.path)
},
fail: function (err) {
console.log(err)
wx.showToast({
title: '網絡錯誤請重試',
icon: 'loading'
})
}
})
})
let avatarurl_width = 60, //繪制的(de)頭像寬度
avatarurl_heigth = 60, //繪制的(de)頭像高度
avatarurl_x = 28, //繪制的(de)頭像在(zài)畫布上(shàng)的(de)位置
avatarurl_y = 36; //繪制的(de)頭像在(zài)畫布上(shàng)的(de)位置
ctx.save(); // 先保存狀态 已便于(yú)畫完圓再用
ctx.beginPath(); //開始繪制
//先畫個(gè)圓 前兩個(gè)參數确定了(le/liǎo)圓心 (x,y) 坐标 第三個(gè)參數是(shì)圓的(de)半徑 四參數是(shì)繪圖方向 默認是(shì)false,即順時(shí)針
ctx.arc(avatarurl_width / 2 + avatarurl_x, avatarurl_heigth / 2 + avatarurl_y, avatarurl_width / 2, 0, Math.PI * 2, false);
ctx.clip(); //畫了(le/liǎo)圓 再剪切 原始畫布中剪切任意形狀和(hé / huò)尺寸。一旦剪切了(le/liǎo)某個(gè)區域,則所有之(zhī)後的(de)繪圖都會被限制在(zài)被剪切的(de)區域内
ctx.drawImage(result[0], avatarurl_x, avatarurl_y, avatarurl_width, avatarurl_heigth); // 推進去圖片
這(zhè)裏舉個(gè)例子(zǐ)說(shuō)下如何繪制文字,比如我要(yào / yāo)繪制如下這(zhè)個(gè)“字”,需要(yào / yāo)動态獲取前面字數的(de)總寬度,這(zhè)樣才能設置“字”的(de)x軸坐标,這(zhè)裏我本來(lái)是(shì)想通過measureText來(lái)測量字體的(de)寬度,但是(shì)在(zài)iOS端第一次獲取的(de)寬度值不(bù)對,關于(yú)這(zhè)個(gè)問題,我還在(zài)微信開發者社區提了(le/liǎo)bug,所以(yǐ)我想用另一個(gè)方法來(lái)實現,就(jiù)是(shì)先獲取正常情況下一個(gè)字的(de)寬度值,然後乘以(yǐ)總字數就(jiù)獲得了(le/liǎo)總寬度,親試是(shì)可以(yǐ)的(de)。

let allReading = 97 / 6 / app.globalData.ratio * wordNumber.toString().length + 325;
ctx.font = 'normal normal 30px sans-serif';
ctx.setFillStyle('#ffffff')
ctx.fillText('字', allReading, 150);
④繪制公衆号二維碼,和(hé / huò)獲取頭像是(shì)一樣的(de),也(yě)是(shì)先通過接口返回圖片網絡地(dì / de)址,然後再通過getImageInfo獲取公衆号二維碼圖片信息
⑤如何繪制小程序碼,具體官網文檔也(yě)給出(chū)生成無限小程序碼接口,通過生成的(de)小程序可以(yǐ)打開任意一個(gè)小程序頁面,并且二維碼永久有效,具體調用哪個(gè)小程序二維碼接口有不(bù)同的(de)應用場景,具體可以(yǐ)看下官方文檔怎麽說(shuō)的(de),也(yě)就(jiù)是(shì)說(shuō)前端通過傳遞參數調取後端接口返回的(de)小程序碼,然後繪制在(zài)畫布上(shàng)(和(hé / huò)上(shàng)面寫的(de)繪制頭像和(hé / huò)公衆号二維碼一樣的(de))
ctx.drawImage('小程序碼的(de)本地(dì / de)地(dì / de)址', x軸, Y軸, 寬, 高)
⑥最終繪制完把canvas畫布轉成圖片并返回圖片地(dì / de)址
wx.canvasToTempFilePath({
canvasId: 'myCanvas',
success: function (res) {
canvasToTempFilePath = res.tempFilePath // 返回的(de)圖片地(dì / de)址保存到(dào)一個(gè)全局變量裏
that.setData({
showShareImg: true
})
wx.showToast({
title: '繪制成功',
})
},
fail: function () {
wx.showToast({
title: '繪制失敗',
})
},
complete: function () {
wx.hideLoading()
wx.hideToast()
}
})
⑦保存到(dào)系統相冊;先判斷用戶是(shì)否開啓用戶授權相冊,處理不(bù)同情況下的(de)結果。比如用戶如果按照正常邏輯授權是(shì)沒問題的(de),但是(shì)有的(de)用戶如果點擊了(le/liǎo)取消授權該如何處理,如果不(bù)處理會出(chū)現一定的(de)問題。所以(yǐ)當用戶點擊取消授權之(zhī)後,來(lái)個(gè)彈框提示,當它再次點擊的(de)時(shí)候,主動跳到(dào)設置引導用戶去開啓授權,從而(ér)達到(dào)保存到(dào)相冊分享朋友圈的(de)目的(de)。
// 獲取用戶是(shì)否開啓用戶授權相冊
if (!openStatus) {
wx.openSetting({
success: (result) => {
if (result) {
if (result.authSetting["scope.writePhotosAlbum"] === true) {
openStatus = true;
wx.saveImageToPhotosAlbum({
filePath: canvasToTempFilePath,
success() {
that.setData({
showShareImg: false
})
wx.showToast({
title: '圖片保存成功,快去分享到(dào)朋友圈吧~',
icon: 'none',
duration: 2000
})
},
fail() {
wx.showToast({
title: '保存失敗',
icon: 'none'
})
}
})
}
}
},
fail: () => { },
complete: () => { }
});
} else {
wx.getSetting({
success(res) {
// 如果沒有則獲取授權
if (!res.authSetting['scope.writePhotosAlbum']) {
wx.authorize({
scope: 'scope.writePhotosAlbum',
success() {
openStatus = true
wx.saveImageToPhotosAlbum({
filePath: canvasToTempFilePath,
success() {
that.setData({
showShareImg: false
})
wx.showToast({
title: '圖片保存成功,快去分享到(dào)朋友圈吧~',
icon: 'none',
duration: 2000
})
},
fail() {
wx.showToast({
title: '保存失敗',
icon: 'none'
})
}
})
},
fail() {
// 如果用戶拒絕過或沒有授權,則再次打開授權窗口
openStatus = false
console.log('請設置允許訪問相冊')
wx.showToast({
title: '請設置允許訪問相冊',
icon: 'none'
})
}
})
} else {
// 有則直接保存
openStatus = true
wx.saveImageToPhotosAlbum({
filePath: canvasToTempFilePath,
success() {
that.setData({
showShareImg: false
})
wx.showToast({
title: '圖片保存成功,快去分享到(dào)朋友圈吧~',
icon: 'none',
duration: 2000
})
},
fail() {
wx.showToast({
title: '保存失敗',
icon: 'none'
})
}
})
}
},
fail(err) {
console.log(err)
}
})
}
總結
至此所有的(de)步驟都已實現,在(zài)繪制的(de)時(shí)候會遇到(dào)一些異步請求後台返回的(de)數據,所以(yǐ)我用promise和(hé / huò)async和(hé / huò)await進行了(le/liǎo)封裝,确保導出(chū)的(de)圖片信息是(shì)完整的(de)。在(zài)繪制的(de)過程确實遇到(dào)一些坑的(de)地(dì / de)方。比如初開始導出(chū)的(de)圖片比例大(dà)小不(bù)對,還有用measureText測量文字寬度不(bù)對,多次繪制(可能受網絡原因)有時(shí)導出(chū)的(de)圖片上(shàng)的(de)文字顔色會有誤差等。如果你也(yě)遇到(dào)一些比較坑的(de)地(dì / de)方可以(yǐ)一起探讨下做個(gè)記錄