前言:在弄「新城名錄」這個(gè)小程序時(shí)美澳,需要將發(fā)布的信息內(nèi)容生成一張帶小程序碼的海報(bào)竞阐,方便分享和轉(zhuǎn)發(fā)僚害。海報(bào)的形式是參照“微信圈子”里面的樣式硫椰,折騰不了不少時(shí)間,也踩了很多坑萨蚕,故在此記錄下來靶草。
首先看看實(shí)現(xiàn)效果
此小程序是基于uni-app開發(fā)的岳遥,也就是vue那套寫法奕翔,所以將海報(bào)的生成邏輯弄成了單獨(dú)的組件。
整個(gè)實(shí)現(xiàn)流程大致如下圖:
一浩蓉、初始化基本尺寸
通過wx.getSystemInfo獲得窗口信息派继。
因?yàn)閏anvas上繪制文字單位是px,所以要通過像素比來計(jì)算文字大小捻艳。
let _ = this
wx.getSystemInfo ({
success (res) {
_.windowInfo.width = _.canvasStyle.width = res.windowWidth
_.windowInfo.height = res.windowHeight
_windowInfo.ratio = res.pixelRatio / 2
// 根據(jù)像素比驾窟,計(jì)算文字的大小
_.canvasStyle.textDf *= _windowInfo.ratio
_.canvasStyle.textSm *= _windowInfo.ratio
}
})
二、獲取小程序碼
2-1: 生成buffer
獲取小程序碼看文檔就行了沒什么可說的:傳送門
我是通過云函數(shù)獲取小程序碼的Buffer
// 云函數(shù)
app.router('wxacode', async (ctx, next) => {
try {
let rs = await cloud.openapi.wxacode.get({
path: event.path,
width: 100,
is_hyaline: true
})
ctx.body = rs
} catch (err) {
ctx.body = err
}
})
wxacode.createQRCode 和 wxacode.get 兩個(gè)接口加起來最多可生成10萬個(gè)小程序碼讯泣。同一參數(shù)的path是一個(gè)纫普,不限請(qǐng)求次數(shù)。
wxacode.getUnlimited 是無限個(gè)小程序碼好渠,但path有限制昨稼,好像不能帶參數(shù)還沒試過。
2-2: 調(diào)用云函數(shù)獲得buffer并轉(zhuǎn)為base64
wx.cloud.callFunction ({
...
}).then(rs => {
let base64 = wx.arrayBufferToBase64(rs.result.buffer.data || rs.result.buffer)
})
在開發(fā)的過程中莫名其妙的小程序碼就繪制不出來了拳锚,最后發(fā)現(xiàn)這里rs.result.buffer對(duì)象中假栓,一會(huì)有data字段一會(huì)沒有,別問為什么總之遇到了霍掺,最好判斷下匾荆。
let imgSrc = 'data:image/jpeg;base64,' + base64
這里轉(zhuǎn)換的是沒有base64前綴的拌蜘,若要顯示在 <image> 標(biāo)簽上,需要加上前綴牙丽。
2-3:獲得本地臨時(shí)鏈接
用wx.getFileSystemManager().writeFile方法寫入到本地简卧。
在繪制完成之后,通過wx.getFileSystemManager().unlink刪除臨時(shí)文件烤芦。
let filePath = `${wx.env.USER_DATA_PATH}/wxacode.jpeg`
wx.getFileSystemManager().writeFile({
filePath,
data,
encoding: 'base64'
...
})
三举娩、計(jì)算畫布的高度
首先看看海報(bào)的布局
由圖可知畫布的高度=圖片區(qū)域+文字區(qū)域+小程序碼區(qū)域+邊距
3-1:計(jì)算繪制圖片所占的高度
用canvas繪制圖片,首先要將圖片下載成功后才能繪制构罗。
在使用wx.downloadFile下載圖片時(shí)铜涉,如果遇到錯(cuò)誤:downloadFile:fail url not in domain list
那么就要在小程序管理后臺(tái)中:開發(fā)>開發(fā)設(shè)置>服務(wù)器域名 去設(shè)置downloadFile合法域名
如果用到了云存儲(chǔ),合法域名就在 云開發(fā)>存儲(chǔ) 中找到文件的https的下載地址
圖片下載完成后遂唧,通過wx.getImageInfo來獲得圖片的尺寸芙代,然后根據(jù)數(shù)量不同采用不同的排版方式。
在這里獲取到圖片信息時(shí)盖彭,就計(jì)算好坐標(biāo)纹烹、尺寸暫存起來,等繪制的時(shí)候直接使用即可谬泌。
3-2:計(jì)算繪制文字所占的高度
繪制文字主要的問題是滔韵,canvas是沒有自動(dòng)換行的逻谦,所以要把文字一個(gè)個(gè)的取出來掌实,然后計(jì)算寬度。
小程序提供了測量文本尺寸信息的接口:CanvasContext.measureText
這個(gè)玩意呢不建議用邦马,因?yàn)樵谡鏅C(jī)測試時(shí)贱鼻,這個(gè)接口的運(yùn)算速度啊慢得要死,文字一多簡直不能玩了滋将。
后面找了個(gè)現(xiàn)成的函數(shù)來獲取文本的寬度邻悬。原文鏈接
同樣在這里獲取文字寬度信息的同時(shí),將坐標(biāo)計(jì)算好暫存起來随闽,等繪制的時(shí)候直接使用即可父丰。
最后是小程序區(qū)域的高度,是固定高度100
四掘宪、開始繪制
4-1: 獲取CanvasContext
首先創(chuàng)建 canvas 的繪圖上下文 CanvasContext 對(duì)象
注意這里要將this參數(shù)帶上
在自定義組件下蛾扇,當(dāng)前組件實(shí)例的this,表示在這個(gè)自定義組件下查找擁有 canvas-id 的 canvas 魏滚,如果省略則不在任何自定義組件內(nèi)查找
let ctx = wx.createCanvasContext ('mycanvas', this)
然后就是按順序繪制圖片镀首、文字、小程序碼區(qū)域
4-2: 導(dǎo)出圖片
繪制完成之后就需要用wx.canvasToTempFilePath鼠次,將畫布的內(nèi)容導(dǎo)出成指定大小的圖片更哄。
官方文檔說了:在 draw() 回調(diào)里調(diào)用該方法才能保證圖片導(dǎo)出成功芋齿。
理論上應(yīng)該是如下這樣子:
ctx.draw(true, () => {
wx.canvasToTempFilePath({ ... })
})
但是,這個(gè)回調(diào)它根本不執(zhí)行呀成翩!
后面查到的是說:繪制速度太快(what ???) 無法進(jìn)入canvas.draw的回調(diào)函數(shù)觅捆,需要在外層套個(gè)setTimeout。
let _ = this
ctx.draw(true, setTimeout(() => {
wx.canvasToTempFilePath ({
fileType: 'jpg',
canvasId: 'mycanvas',
x: 0,
y: 0,
width: _.canvasStyle.width,
height: _.canvasStyle.height,
success (res) {
_.imgSrc = res.tempFilePath
}
}, _)
}, 300))
在使用wx.canvasToTempFilePath記得把this傳入進(jìn)去麻敌,同時(shí)最好指定好畫布區(qū)域的寬高惠拭,不然可能存在圖片空白的情況。
4-3: 預(yù)覽圖片
繪制的<canvas>節(jié)點(diǎn)是隱藏在屏幕外的庸论,真正用于預(yù)覽的是<image>節(jié)點(diǎn)
如圖:
為什么不直接用<canvas>來預(yù)覽职辅?
因?yàn)轭A(yù)覽的尺寸和canvas的尺寸不一樣,所以就要做縮放聂示,將<canvas>標(biāo)簽用css3 transform:scale(.8, .8) 在真機(jī)上是沒有作用的S蛐!鱼喉!
所以只能把wx.canvasToTempFilePath導(dǎo)出的圖片路徑秀鞭,放到<image>上來做顯示。
但是扛禽,
導(dǎo)出來的圖片有可能存在留白區(qū)域锋边,像就是沒繪制完,這里就要如4-2所說编曼,把導(dǎo)出寫到draw回調(diào)中豆巨,同時(shí)一定要把延時(shí)加上。
五掐场、保存圖片
當(dāng)你滿懷欣喜的爬完上面的坑往扔,以為調(diào)用一下wx.saveImageToPhotosAlbum接口把圖片保存完,就大功告成了碼熊户?
不存在的F继拧!嚷堡!
此接口需要用戶授權(quán)蝗罗,才能成功將圖片保存,而如果用戶不小心點(diǎn)了拒絕授權(quán)蝌戒,那么是不是要手動(dòng)調(diào)用下跳轉(zhuǎn)到授權(quán)設(shè)置頁面串塑。
wx.getSetting({
success(res) {
if (!res.authSetting['scope.writePhotosAlbum']) {
wx.authorize({
scope: 'scope.writePhotosAlbum',
...
fail () {
wx.openSetting(...)
}
})
}
}
})
跳轉(zhuǎn)授權(quán)?跳轉(zhuǎn)得了屁勒瓶颠!
翻看wx.openSetting的官方文檔拟赊,有那么一小撮字告訴你:用戶發(fā)生點(diǎn)擊行為后,才可以跳轉(zhuǎn)打開設(shè)置頁
所以你還得整個(gè)對(duì)話框粹淋,讓用戶點(diǎn)一下子吸祟。
wx.getSetting({
success(res) {
// 進(jìn)行授權(quán)檢測瑟慈,未授權(quán)則進(jìn)行彈層授權(quán)
if (!res.authSetting['scope.writePhotosAlbum']) {
wx.authorize({
scope: 'scope.writePhotosAlbum',
// 拒絕授權(quán)時(shí),則進(jìn)入手機(jī)設(shè)置頁面屋匕,可進(jìn)行授權(quán)設(shè)置
fail(err) {
wx.showModal({
title: '提示',
content: '需要您授權(quán)才能保存到相冊',
success: (res) => {
if (res.confirm) {
wx.openSetting({
...
})
}
}
})
}
})
}
}
})
最后附上完整代碼地址:https://github.com/yiPian/poster