目標:在微信小程序中頁面有一個按鈕,點擊后生成一張海報,點擊保存,圖片保存到本地相冊
海報樣式如下:
通過觀摩別人代碼,分析這張海報,難點有四個,一是背景的圓角,canvas並沒有一個api是畫圓角的,二是中間的兩行標題,這里應該是動態的,可能一行可能兩行,三是圓形頭像處理,四是,畫出的海報如何在點擊下載的時候很好的布局。最終成果如下:
wxml:
<view class="btn" bindtap="share">點我生成海報</view> <canvas canvas-id="firstCanvas" class="canvas-exp"></canvas> <view hidden='{{previewHidden}}' class='preview'> <image src='{{preurl}}' mode='widthFix' class='previewImg' style="height:{{currentLineHeight}}px"></image> <button bindtap='eventSave' class="btnSave">保存到相冊</button> </view>
wxss:
.btn { text-align: center; margin-top: 300rpx; } .btnSave { width: 408rpx; height: 92rpx; line-height: 92rpx; background: #FFFFFF; border-radius: 46rpx; text-align: center; font-size: 36rpx; font-weight: 600; color: #FF6C5D; margin-top:30rpx; } .preview{ width: 100%; height: 100%; background: rgba(0,0,0,0.7); position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 10; } .canvas-exp { position: fixed; bottom: 0; right: 100%; width: 100%; height: 100%; background: transparent; } .previewImg { width: 560rpx; border-radius:56rpx; margin-top: 64rpx; margin-left:12.8%; }
js中的data:
data: { windowW: 0, windowH: 0, show: false, bgpic: '', propic: '', qCord: '', picImg: '', previewHidden: true, preurl: '', currentLineHeight:0 },
js中的onLoad:
onLoad: function (options) { let that = this // 獲取設備寬高 wx.getSystemInfo({ success: function (res) { that.setData({ windowW: res.windowWidth, windowH: res.windowHeight }) } }) that.setData({ bgpic: '../../../assets/head.png', propic: '../../../assets/postbg.png', qCord: '../../../assets/code.png', picImg: '../../../assets/backImg.png' }) that.drawCanvas() }
在onLoad,海報中的圖片資源應該是動態的可以在這請求好,在這邊已經生成海報,考慮海報在點擊以后再去生成要等待時間過長,圖片資源下載失敗還可以在頁面加載之后再去請求。
canvas繪制海報函數:
drawCanvas() { let that = this let windowW = that.data.windowW let windowH = that.data.windowH let ctx = wx.createCanvasContext('firstCanvas') let text = '健康大使就發生了的打掃房間了放大設計方案放大鏡雙方就安分' let row = [] let strLen = text.length let rowNum = 0 // 計算文字行數 row.push(text.slice(0, 12)) rowNum = 1 if (strLen > 12 && (strLen <= 24)) { rowNum = 0 row.push(text.slice(12, 24)) } if (strLen > 24) { rowNum = 0 row.push(text.slice(12, 22) + '...') } ctx.drawImage(that.data.propic, (windowW - 279) / 2, 32, 279, (460 - rowNum * 26)) that.setData({ currentLineHeight: 460 - rowNum * 26 }) ctx.setFillStyle('#FFFFFF') // 白色圓角背景 let x = (windowW - 256) / 2 let y = 62 let r = 24 let w = 256 let h = 344 - rowNum * 26 ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5) ctx.moveTo(x + r, y) ctx.lineTo(x + w - r, y) ctx.lineTo(x + w, y + r) ctx.arc(x + w - r, y + r, r, Math.PI * 1.5, Math.PI * 2) ctx.lineTo(x + w, y + h - r) ctx.lineTo(x + w - r, y + h) ctx.arc(x + w - r, y + h - r, r, 0, Math.PI * 0.5) ctx.lineTo(x + r, y + h) ctx.lineTo(x, y + h - r) ctx.arc(x + r, y + h - r, r, Math.PI * 0.5, Math.PI) ctx.lineTo(x, y + r) ctx.lineTo(x + r, y) ctx.fill() // 識別小程序二維碼 ctx.drawImage(that.data.qCord, (windowW - 236) / 2 + 173, 32 + 387, 60, 60) ctx.setFillStyle("#ffffff") ctx.setFontSize(13) ctx.fillText('長按識別二維碼', (windowW - 236) / 2, 32 + 412 - rowNum * 26) ctx.setFillStyle("#ffffff") ctx.setFontSize(13) ctx.fillText('查看TA發布的全部精華內容', (windowW - 236) / 2, 32 + 433 - rowNum * 26) ctx.setFillStyle("#0B0D0E") ctx.setFontSize(15) ctx.fillText('健康醫生', (windowW - 218) / 2 + 48, 32 + 345 - rowNum * 26) let collectImg = '../../../assets/hexagon.png' ctx.drawImage(collectImg, (windowW - 218) / 2, 92, 18, 20) ctx.font = 'normal bold 14px PingFang-SC-Medium'; ctx.setFillStyle("#AF8B43") ctx.fillText('精選內容', (windowW - 218) / 2 + 24, 108) ctx.drawImage(that.data.picImg, (windowW - 218) / 2, 32 + 160 - rowNum * 26, 213, 136) // 圓形頭像 let avatarurl_width = 38 let avatarurl_heigth = 38 let avatarurl_x = (windowW - 218) / 2 let avatarurl_y = 352 - rowNum * 26 ctx.save() ctx.beginPath() ctx.arc(avatarurl_width / 2 + avatarurl_x, avatarurl_heigth / 2 + avatarurl_y, avatarurl_width / 2, 0, Math.PI * 2, false) ctx.clip() ctx.drawImage(that.data.bgpic, avatarurl_x, avatarurl_y, avatarurl_width, avatarurl_heigth) ctx.restore() for (let b = 0; b < row.length; b++) { ctx.font = 'normal bold 22px PingFang-SC-Medium'; ctx.setFillStyle("#0B0D0E") ctx.fillText(row[b], (windowW - 218) / 2, 144 + b * 26, 218) } ctx.draw() },
由於標題數據動態,不知道是一行還是兩行,如果超過兩行只顯示兩行,且以省略號結尾,這會導致整張海報的高度發生改變,整個布局都會受影響,因而先計算標題行數。若先繪制標題,會被后面的白色背景覆蓋,后文的文字也會帶上標題的加粗等樣式,所以放在了最后面。
點擊生成海報,把海報轉成圖片顯示在頁面:
share: function () { var that = this wx.showLoading({ title: '努力生成中...' }) wx.canvasToTempFilePath({ x: (that.data.windowW - 279) / 2, y: 32, width: 279, height: that.data.currentLineHeight, canvasId: 'firstCanvas', fileType: 'jpg', quality: 1, success: function (res) { console.log(res.tempFilePath); that.setData({ preurl: res.tempFilePath, previewHidden: false, }) wx.hideLoading() }, fail: function (res) { console.log(res) } }) }
本來設計頁面是點擊生成海報,canvas跟保存海報按鈕放在灰色背景上,但是在不同的設備下,保存海報按鈕跟海報距離相差太大,因而改成頁面加載,在可視范圍之外把canvas畫好,點擊生成海報時,只是把海報轉成圖片,變成圖片和按鈕的布局
點擊保存海報:
eventSave() { let that =this wx.saveImageToPhotosAlbum({ filePath: this.data.preurl, success(res) { wx.showToast({ title: '保存圖片成功', icon: 'success', duration: 2000 }) that.setData({ previewHidden: true, }) } }) }