https://github.com/jasondu/wx...javascript
小程序分享到朋友圈只能使用小程序碼海報來實現,生成小程序碼的方式有兩種,一種是使用後端方式,一種是使用小程序自帶的canvas生成;後端的方式開發難度大,因爲生成圖片耗用內存比較大對服務端也是不小的壓力;因此使用小程序的canvas是一個不錯的選擇,但因爲canvas水比較深,坑比較多,還有不一樣海報須要重現寫渲染流程,致使代碼冗餘難以維護,加上不一樣設備版本的狀況不同,所以小程序海報生成組件的需求十分迫切。css
在實際開發中,我發現海報中的元素無非一下幾種,只要實現這幾種,就能夠經過一份配置文件生成各類各樣的海報了。java
canvas繪製使用的是px單位,但不一樣設備的px是須要換算的,因此在組件中統一使用rpx單位,這裏就涉及到單位怎麼換算問題。git
經過wx.getSystemInfoSync獲取設備屏幕尺寸,從而獲得比例,進而作轉換,代碼以下:github
const sysInfo = wx.getSystemInfoSync(); const screenWidth = sysInfo.screenWidth; this.factor = screenWidth / 750; // 獲取比例 function toPx(rpx) { // rpx轉px return rpx * this.factor; } function toRpx(px) { // px轉rpx return px / this.factor; },
在繪製海報過程時,咱們不想讓用戶看到canvas,因此咱們必須把canvas隱藏起來,一開始想到的是使用display:none; 但這樣在轉化成圖片時會空白,因此這個是行不通的,因此只能控制canvas的絕對定位,將其移出可視界面,代碼以下:canvas
.canvas.pro { position: absolute; bottom: 0; left: -9999rpx; }
因爲canvas沒有提供現成的圓角api,因此咱們只能手工畫啦,實際上圓角矩形就是由4條線(黃色)和4個圓弧(紅色)組成的,以下:小程序
圓弧可使用canvasContext.arcTo這個api實現,這個api的入參由兩個控制點一個半徑組成,對應上圖的示例後端
canvasContext.arcTo(x1, y1, x2, y2, r)
接下來咱們就能夠很是輕鬆的寫出生成圓角矩形的函數啦api
/** * 畫圓角矩形 */ _drawRadiusRect(x, y, w, h, r) { const br = r / 2; this.ctx.beginPath(); this.ctx.moveTo(this.toPx(x + br), this.toPx(y)); // 移動到左上角的點 this.ctx.lineTo(this.toPx(x + w - br), this.toPx(y)); // 畫上邊的線 this.ctx.arcTo(this.toPx(x + w), this.toPx(y), this.toPx(x + w), this.toPx(y + br), this.toPx(br)); // 畫右上角的弧 this.ctx.lineTo(this.toPx(x + w), this.toPx(y + h - br)); // 畫右邊的線 this.ctx.arcTo(this.toPx(x + w), this.toPx(y + h), this.toPx(x + w - br), this.toPx(y + h), this.toPx(br)); // 畫右下角的弧 this.ctx.lineTo(this.toPx(x + br), this.toPx(y + h)); // 畫下邊的線 this.ctx.arcTo(this.toPx(x), this.toPx(y + h), this.toPx(x), this.toPx(y + h - br), this.toPx(br)); // 畫左下角的弧 this.ctx.lineTo(this.toPx(x), this.toPx(y + br)); // 畫左邊的線 this.ctx.arcTo(this.toPx(x), this.toPx(y), this.toPx(x + br), this.toPx(y), this.toPx(br)); // 畫左上角的弧 }
若是是畫線框就使用this.ctx.stroke();
數組
若是是畫色塊就使用this.ctx.fill();
若是是圓角圖片就使用
this.ctx.clip(); this.ctx.drawImage(***);
clip() 方法從原始畫布中剪切任意形狀和尺寸。一旦剪切了某個區域,則全部以後的繪圖都會被限制在被剪切的區域內(不能訪問畫布上的其餘區域)。能夠在使用 clip() 方法前經過使用 save() 方法對當前畫布區域進行保存,並在之後的任意時間對其進行恢復(經過 restore() 方法)。
若是是連續多段不一樣格式的文字,若是讓用戶每段文字都指定座標是不現實的,由於上一段文字的長度是不固定的,這裏的解決方案是使用ctx.measureText
(基礎庫 1.9.90 開始支持)Api來計算一段文字的寬度,記住這裏返回寬度的單位是px(坑),從而知道下一段文字的座標。
設置文字的寬度,經過ctx.measureText
知道文字的寬度,若是超出設定的寬度,超出部分使用「...」代替;對於多行文字,經測試發現字體的高度大約等於字體大小,並提供lineHeight參數讓用戶能夠自定義行高,這樣咱們就能夠知道下一行的y軸座標了。
這個一樣使用ctx.measureText
接口,從而控制矩形的寬度,固然這裏用戶還能夠設置paddingLeft和paddingRight字段;
文字的垂直居中問題能夠設置文字的基線對齊方式爲middle(this.ctx.setTextBaseline('middle');
),設置文字的座標爲矩形的中線就能夠了;水平居中this.ctx.setTextAlign('center');
;
因爲canvas沒有Api能夠設置繪製元素的層級,只能是根據後繪製層級高於前面繪製的方式,因此須要用戶傳入zIndex字段,利用數組排序(Array.prototype.sort)後再根據順序繪製。
繪製圖片咱們使用ctx.drawImage()
API;
若是使用drawImage(dx, dy, dWidth, dHeight)
,圖片會壓縮尺寸以適應繪製的尺寸,圖片會變形,以下圖:
在基礎庫1.9.0起支持drawImage(sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
,sx和sy是源圖像的矩形選擇框左上角的座標,sWidth和sHeight是源圖像的矩形選擇框的寬度和高度,以下圖:
若是繪製尺寸比源圖尺寸寬,那麼繪製尺寸的寬度就等於源圖寬度;反之,繪製尺寸比源圖尺寸高,那麼繪製尺寸的高度等於源圖高度;
咱們能夠經過wx.getImageInfo
Api獲取源圖的尺寸;
在canvas繪製完成後調用wx.canvasToTempFilePath
Api將canvas轉爲圖片輸出,這樣須要注意,wx.canvasToTempFilePath
須要寫在this.ctx.draw
的回調中,而且在組件中使用這個接口須要在第二個入參傳入this(坑),以下
this.ctx.draw(false, () => { wx.canvasToTempFilePath({ canvasId: 'canvasid', success: (res) => { wx.hideLoading(); this.triggerEvent('success', res.tempFilePath); }, fail: (err) => { wx.hideLoading(); this.triggerEvent('fail', err); } }, this); });
在IOS 6.6.7版本中clip方法連續裁剪圖片時,只有第一張有效,這是微信的bug,官方也證明了(https://developers.weixin.qq....)
咱們可使用wx.createCanvasContext
獲取小程序實例,但在組件中使用切記第二個參數須要帶上this,以下
this.ctx = wx.createCanvasContext('canvasid', this);