小程序分享到朋友圈只能使用小程序碼海報來實現(xiàn),生成小程序碼的方式有兩種,一種是使用后端方式,一種是使用小程序自帶的canvas生成;后端的方式開發(fā)難度大,由于生成圖片耗用內(nèi)存比較大對服務(wù)端也是不小的壓力;所以使用小程序的canvas是一個不錯的選擇,但由于canvas水比較深,坑比較多,還有不同海報需要重現(xiàn)寫渲染流程,導(dǎo)致代碼冗余難以維護(hù),加上不同設(shè)備版本的情況不一樣,因此小程序海報生成組件的需求十分迫切。
在實際開發(fā)中,我發(fā)現(xiàn)海報中的元素?zé)o非一下幾種,只要實現(xiàn)這幾種,就可以通過一份配置文件生成各種各樣的海報了。
canvas繪制使用的是px單位,但不同設(shè)備的px是需要換算的,所以在組件中統(tǒng)一使用rpx單位,這里就涉及到單位怎么換算問題。
通過wx.getSystemInfoSync獲取設(shè)備屏幕尺寸,從而得到比例,進(jìn)而做轉(zhuǎn)換,代碼如下:
const sysInfo = wx.getSystemInfoSync(); const screenWidth = sysInfo.screenWidth; this.factor = screenWidth / 750; // 獲取比例 function toPx(rpx) { // rpx轉(zhuǎn)px return rpx * this.factor; } function toRpx(px) { // px轉(zhuǎn)rpx return px / this.factor; |
在繪制海報過程時,我們不想讓用戶看到canvas,所以我們必須把canvas隱藏起來,一開始想到的是使用display:none; 但這樣在轉(zhuǎn)化成圖片時會空白,所以這個是行不通的,所以只能控制canvas的絕對定位,將其移出可視界面,代碼如下:
.canvas.pro { position: absolute; bottom: 0; left: -9999rpx; } |
由于canvas沒有提供現(xiàn)成的圓角api,所以我們只能手工畫啦,實際上圓角矩形就是由4條線(黃色)和4個圓?。t色)組成的,如下:
圓弧可以使用canvasContext.arcTo這個api實現(xiàn),這個api的入?yún)⒂蓛蓚€控制點一個半徑組成,對應(yīng)上圖的示例
canvasContext.arcTo(x1, y1, x2, y2, r)
接下來我們就可以非常輕松的寫出生成圓角矩形的函數(shù)啦
/** * 畫圓角矩形 */ _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() 方法從原始畫布中剪切任意形狀和尺寸。一旦剪切了某個區(qū)域,則所有之后的繪圖都會被限制在被剪切的區(qū)域內(nèi)(不能訪問畫布上的其他區(qū)域)??梢栽谑褂?clip() 方法前通過使用 save() 方法對當(dāng)前畫布區(qū)域進(jìn)行保存,并在以后的任意時間對其進(jìn)行恢復(fù)(通過 restore() 方法)。
如果是連續(xù)多段不同格式的文字,如果讓用戶每段文字都指定坐標(biāo)是不現(xiàn)實的,因為上一段文字的長度是不固定的,這里的解決方案是使用 ctx.measureText (基礎(chǔ)庫 1.9.90 開始支持)Api來計算一段文字的寬度,記住這里返回寬度的單位是px( 坑 ),從而知道下一段文字的坐標(biāo)。
設(shè)置文字的寬度,通過 ctx.measureText 知道文字的寬度,如果超出設(shè)定的寬度,超出部分使用“...”代替;對于多行文字,經(jīng)測試發(fā)現(xiàn)字體的高度大約等于字體大小,并提供lineHeight參數(shù)讓用戶可以自定義行高,這樣我們就可以知道下一行的y軸坐標(biāo)了。
這個同樣使用 ctx.measureText 接口,從而控制矩形的寬度,當(dāng)然這里用戶還可以設(shè)置paddingLeft和paddingRight字段;
文字的垂直居中問題可以設(shè)置文字的基線對齊方式為middle( this.ctx.setTextBaseline('middle'); ),設(shè)置文字的坐標(biāo)為矩形的中線就可以了;水平居中 this.ctx.setTextAlign('center'); ;
由于canvas沒有Api可以設(shè)置繪制元素的層級,只能是根據(jù)后繪制層級高于前面繪制的方式,所以需要用戶傳入zIndex字段,利用數(shù)組排序(Array.prototype.sort)后再根據(jù)順序繪制。
繪制圖片我們使用 ctx.drawImage() API;
如果使用 drawImage(dx, dy, dWidth, dHeight) ,圖片會壓縮尺寸以適應(yīng)繪制的尺寸,圖片會變形,如下圖:
在基礎(chǔ)庫1.9.0起支持 drawImage(sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) ,sx和sy是源圖像的矩形選擇框左上角的坐標(biāo),sWidth和sHeight是源圖像的矩形選擇框的寬度和高度,如下圖:
如果繪制尺寸比源圖尺寸寬,那么繪制尺寸的寬度就等于源圖寬度;反之,繪制尺寸比源圖尺寸高,那么繪制尺寸的高度等于源圖高度;
我們可以通過 wx.getImageInfo Api獲取源圖的尺寸;
在canvas繪制完成后調(diào)用 wx.canvasToTempFilePath Api將canvas轉(zhuǎn)為圖片輸出,這樣需要注意, wx.canvasToTempFilePath 需要寫在 this.ctx.draw 的回調(diào)中,并且在組件中使用這個接口需要在第二個入?yún)魅雝his( 坑 ),如下
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); }); |
工作日 8:30-12:00 14:30-18:00
周六及部分節(jié)假日提供值班服務(wù)