使用canvas生成微信小程式海報
很多小程式為了更好的推廣,都會有生成海報的功能。在多數時候,海報都是後端生成好,然後前端直接下載展示,讓使用者儲存到本地就ok。不過有的時候也是前端自己去拿資料然後生成海報,供使用者儲存,恰好自己搗鼓過那麼一下,所以在這裡分享一下前端生成海報的方法。
說直接一點,所謂的生成海報,無非就是將多張圖片合成一張,或者在圖片上面加點文字之類的東西,例如商品價格,商品資訊之類的。既然是要合成圖片,那最先想到的當然就是canvas畫布了。因為我公司的專案程式碼不方便往這上面貼,所以我就新建了一個小程式的專案,自然而然就有那麼一點簡化。
pages/index/index.wxml檔案:
1 <viewclass="poster-wrap" wx:if="{{showCanvas}}"> 2 <canvas class="canvas-style" canvas-id="myCanvas"></canvas> 3 <view bindtap="savePoster" class="save-btn">儲存到本地</view> 4 </view> 5 <button bindtap="clickCreate" size="mini" type="primary">點我生成海報</button>
pages/index/index.wxss檔案:
canvas彈出層的寬高用的是px單位,而不是rpx,如果用rpx,那在繪製的時候就不太好計算了。因此這裡最好以px為單位。到後面匯出或者設定海報寬高時都會方便。而且那種海報圖片的大小,基本上不會大小不一,一般都是定死的。
1 .poster-wrap { 2 width:240px; 3 height:430px; 4 position: fixed; 5 top:50%; 6 left:50%; 7 transform: translate(-50%,-50%); 8 z-index: 99; 9 } 10 .canvas-style { 11 width:100%; 12 height:400px; 13 } 14 .save-btn { 15 width:100%; 16 height:30px; 17 background:rgba(24, 41, 75, 1); 18 text-align: center; 19 line-height:30px; 20 color:#FFF; 21 }
pages/index/index.js檔案:
1 2 import { 3 createPoster 4 } from "../../utils/util.js"; 5 Page({ 6 data: { 7 img: '', // 生成後的海報路徑,儲存到本地時可用,也可用於image標籤展示 8 showCanvas: false, // 是否顯示canvas 9 }, 10 11 // 儲存海報 12 savePoster() { 13 wx.getSetting({ 14 success: (res) => { 15 // 判斷是否有訪問相簿的許可權,沒有的情況下彈出框提示使用者允許授權。 16 if (!res.authSetting['scope.writePhotosAlbum']) { 17 wx.authorize({ 18 scope: 'scope.writePhotosAlbum', 19 success: (aRes) => { 20 wx.saveImageToPhotosAlbum({ 21 filePath: this.data.img, 22 success: () => { 23 wx.showToast({ 24 title: '儲存成功', 25 success:()=>{ 26 this.setData({ 27 showCanvas:false 28 }) 29 } 30 }) 31 }, 32 fail: () => { 33 wx.showToast({ 34 title: '儲存失敗', 35 icon: 'none' 36 }) 37 } 38 }) 39 } 40 }) 41 } else { 42 wx.saveImageToPhotosAlbum({ 43 filePath: this.data.img, 44 success: () => { 45 wx.showToast({ 46 title: '儲存成功', 47 success:()=>{ 48 this.setData({ 49 showCanvas:false 50 }) 51 } 52 }) 53 }, 54 fail: () => { 55 wx.showToast({ 56 title: '儲存失敗', 57 icon: 'none' 58 }) 59 } 60 }) 61 } 62 } 63 }) 64 }, 65 66 // 點選建立海報 67 clickCreate() { 68 this.setData({ 69 showCanvas: true 70 }, () => { 71 // 建立海報 72 createPoster((res) => { 73 this.setData({ 74 img: res.tempFilePath 75 }) 76 }) 77 }) 78 79 }, 80 81 onLoad: function () { 82 83 }, 84 85 })
以上三個檔案就是demo裡面的程式碼了,因為程式碼不多,而且很好看懂,所以也沒有什麼好解釋的。然後把生成海報的那個函式也貼出來:
utils/util.js檔案:
1 /** 2 * @param {回撥函式} fn 3 */ 4 export const createPoster = (fn) => { 5 wx.showLoading({ 6 title: "生成中..." 7 }) 8 const canvas = wx.createCanvasContext("myCanvas") 9 let cW = 240,cH = 400 10 // 背景圖 11 canvas.drawImage("/images/timg (1).jpg",0,0,cW,cH) 12 // 商品名稱 13 canvas.setFillStyle("#FFF") 14 canvas.setFontSize(12) 15 canvas.fillText("青少年T恤衫",30,30) 16 //商品圖片 17 canvas.drawImage("/images/T-shirt.jpg",30,40,cW-60,cH-200) 18 // 商品價格 19 canvas.setFillStyle("red") 20 canvas.fillText("¥102",cW/2-20,cH-140) 21 // 商品介紹 22 canvas.setFillStyle("#FFF") 23 canvas.fillText("這是一件很好看的T恤衫,快來買咯!",20,cH-120) 24 canvas.setTextAlign("center") 25 // 使用者頭像 26 canvas.save() 27 canvas.arc(35,325,30,0,2*Math.PI) 28 canvas.clip() 29 canvas.drawImage("/images/avatar.jpg",5,295,60,60) 30 canvas.restore() 31 // 提示文字 32 canvas.setFontSize(12) 33 canvas.setFillStyle("#FFF") 34 canvas.setTextAlign("center") 35 canvas.fillText("長按識別二維碼",115,330) 36 // 二維碼 37 canvas.save() 38 canvas.arc(195,325,35,0,2*Math.PI) 39 canvas.clip() 40 canvas.drawImage("/images/baidu.png",160,290,70,70) 41 canvas.restore() 42 43 // 開始繪製 44 canvas.draw(true,()=>{ 45 // 將畫布中的內容匯出為圖片 46 wx.canvasToTempFilePath({ 47 canvasId: "myCanvas", 48 destWidth:cW*3, // 匯出後的圖片寬度 49 destHeight:cH*3, // 匯出後的圖片高度 50 success:(res)=>{ 51 wx.hideLoading() 52 // 通過回撥函式返回生成後的圖片 53 fn && fn(res) 54 } 55 }) 56 }) 57 }
在繪製使用者頭像的時候,先呼叫save儲存了一下畫布的狀態,然後呼叫arc繪製了一個x座標35,y座標325,半徑30的圓形,clip函式呼叫後之後繪製的東西都會被限制在該圓圈之內,多數海報上的使用者頭像都是圓形的,因此我這裡也搞成圓形的。
clip之後呼叫drawImage畫了一張頭像,頭像的位置應該是剛好覆蓋掉圓圈,那自然就形成了圓形的頭像,所以,頭像的x和y座標以及大小(直徑)應該先和圓圈保持一致,因此這裡計算頭像x和y兩個點的位置為:x和y分別減掉圓圈的半徑,那樣頭像就剛好覆蓋掉圓圈。在完了以後,再把狀態恢復回去,也就是呼叫restore函式,方便之後的內容繪製。
我這裡繪製那些東西的時候,比較簡單粗暴,一步一步繪製的。或許繪製的程式碼可能有點臃腫了,我這裡用的是測試號,從網上找的圖片無法通過小程式的api去下載,需要配域名,測試號沒法配,所以我就直接把用到的幾張圖片直接下載放到了images資料夾。正常情況下是需要通過wx.downLoadFile去先下載圖片的,我這裡被限制了,所以簡化了很多。在真正小程式程式碼裡面去搞這些的時候,其實背景圖很重要,因為可以少畫一些不必要的東西,而只需要關注變動的,比如價格,商品圖片,介紹,二維碼以及使用者頭像,其餘的不變的,按理說都應該是由設計弄一張產品看得上的背景圖片,而海報上不變的東西,像什麼分割線,背景圖案什麼的,都應該是背景圖的一部分,我們只需要在背景圖上去把那些變動的商品使用者相關的東西給合上去就好。
在繪製好了之後,呼叫canvasToTempFilePath函式匯出畫布中的內容,匯出的形式是一張圖片,預設為png格式,小程式裡面只有兩種格式可選,png和jpg。
這裡有個需要注意的地方,destWidth和destHeight是匯出後的圖片大小,這裡之所以在canvas背景圖寬高的基礎上乘以3,是為了解決匯出後圖片模糊失貞的問題,出現圖片模糊的原因在於canvas繪製的時候是邏輯畫素,而destWidth和destHeight匯出寬高時是物理畫素。物理畫素和邏輯畫素的資料有很多,百度隨便搜一大把。
在匯出圖片成功之後,會執行success回撥,不過這裡的資料並不是在這裡直接取的,而是需要暴露給外面,因此這裡通過回撥函式的方式去做。
其實canvas是一個很強悍的東西,繪製這麼一些靜態的海報連冰山一角都算不上,我覺得應該花時間去深度瞭解一下。
順便附上這個demo地址:https://github.com/Planet-Tao/wechat-poster
拉到本地直接用測試號就行了。可以直接生成,這裡只是提供了一種方式,僅供參考。所以對於樣式以及圖片就顯得很隨意了。如圖: