小程式海報元件
需求
小程式分享到朋友圈只能使用小程式碼海報來實現,生成小程式碼的方式有兩種,一種是使用後端方式,一種是使用小程式自帶的canvas生成;後端的方式開發難度大,由於生成圖片耗用記憶體比較大對服務端也是不小的壓力;所以使用小程式的canvas是一個不錯的選擇,但由於canvas水比較深,坑比較多,還有不同海報需要重現寫渲染流程,導致程式碼冗餘難以維護,加上不同裝置版本的情況不一樣,因此小程式海報生成元件的需求十分迫切。
在實際開發中,我發現海報中的元素無非一下幾種,只要實現這幾種,就可以通過一份配置檔案生成各種各樣的海報了。
海報中的元素分類
要解決的問題
-
單位問題
-
canvas隱藏問題
-
圓角矩形、圓角圖片
-
多段文字
-
超長文字和多行文字縮略問題
-
矩形包含文字
-
多個元素間的層級問題
-
圖片尺寸和渲染尺寸不一致問題
-
canvas轉圖片
-
IOS 6.6.7 clip問題
-
關於獲取canvas例項
單位問題
canvas繪製使用的是px單位,但不同裝置的px是需要換算的,所以在元件中統一使用rpx單位,這裡就涉及到單位怎麼換算問題。
通過wx.getSystemInfoSync獲取裝置螢幕尺寸,從而得到比例,進而做轉換,程式碼如下:
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,所以我們必須把canvas隱藏起來,一開始想到的是使用display:none; 但這樣在轉化成圖片時會空白,所以這個是行不通的,所以只能控制canvas的絕對定位,將其移出可視介面,程式碼如下:
.canvas.pro {
position: absolute;
bottom: 0;
left: -9999rpx;
}
複製程式碼
圓角矩形、圓角圖片
由於canvas沒有提供現成的圓角api,所以我們只能手工畫啦,實際上圓角矩形就是由4條線(黃色)和4個圓弧(紅色)組成的,如下:
圓弧可以使用canvasContext.arcTo這個api實現,這個api的入參由兩個控制點一個半徑組成,對應上圖的示例
canvasContext.arcTo(x1, y1, x2, y2, r)
複製程式碼
接下來我們就可以非常輕鬆的寫出生成圓角矩形的函式啦
/**
* 畫圓角矩形
*/
_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轉圖片
在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問題
在IOS 6.6.7版本中clip方法連續裁剪圖片時,只有第一張有效,這是微信的bug,官方也證實了(developers.weixin.qq.com/community/d…
關於獲取canvas例項
我們可以使用wx.createCanvasContext
獲取小程式例項,但在元件中使用切記第二個引數需要帶上this,如下
this.ctx = wx.createCanvasContext('canvasid', this);
複製程式碼