最近智酷君在做[小程式]canvas生成海報的專案中遇到一些棘手的問題,在網上查閱了各種資料,也踩扁了各種坑,智酷君希望把這些“填坑”經驗整理一下分享出來,避免後來的兄弟重複“掉坑”。
這是一個大致的原型圖,下面來看下如何製作這個海報,以及整體的思路。
[程式碼片段]Canvas生成海報實戰demo
demo的微信路徑:developers.weixin.qq.com/s/Q74OU3m57…
demo的ID:Q74OU3m57c9x
如果你裝了IDE工具,可以直接訪問上面的demo路徑
通過程式碼片段將demo的ID輸入進去也可新增:
下面分享下主要的程式碼內容和“填坑現場”:
一、新增字型
developers.weixin.qq.com/miniprogram…
canvasContext.font = value //示例
ctx.font = `normal bold 20px sans-serif`//設定字型大小,預設10
ctx.setTextAlign('left');
ctx.setTextBaseline("top");
ctx.fillText("《智酷方程式》專注研究和分享前端技術", 50, 15, 250)//繪製文字
複製程式碼
符合 CSS font 語法的 DOMString 字串,至少需要提供字型大小和字型族名。預設值為 10px sans-serif
文字過長在canvas下換行問題處理(最多兩行,超過“...”代替)
ctx.setTextAlign('left');
ctx.setFillStyle('#000');//文字顏色:預設黑色
ctx.font = `normal bold 18px sans-serif`//設定字型大小,預設10
let canvasTitleArray = canvasTitle.split("");
let firstTitle = ""; //第一行字
let secondTitle = ""; //第二行字
for (let i = 0; i < canvasTitleArray.length; i++) {
let element = canvasTitleArray[i];
let firstWidth = ctx.measureText(firstTitle).width;
//console.log(ctx.measureText(firstTitle).width);
if (firstWidth > 260) {
let secondWidth = ctx.measureText(secondTitle).width;
//第二行字數超過,變為...
if (secondWidth > 260) {
secondTitle += "...";
break;
} else {
secondTitle += element;
}
} else {
firstTitle += element;
}
}
//第一行文字
ctx.fillText(firstTitle, 20, 278, 280)//繪製文字
//第二行問題
if (secondTitle) {
ctx.fillText(secondTitle, 20, 300, 280)//繪製文字
}
複製程式碼
通過 ctx.measureText 這個方法可以判斷文字的寬度,然後進行切割。 (一行字允許寬度為280時,判斷需要寫小點,比如260)
二、獲取臨時地址並設定圖片
let mainImg = "https://demo.com/url.jpg";
wx.getImageInfo({
src: mainImg,//伺服器返回的圖片地址
success: function (res) {
//處理圖片縱橫比例過大或者過小的問題!!!
let h = res.height;
let w = res.width;
let setHeight = 280, //預設源圖擷取的區域
setWidth = 220; //預設源圖擷取的區域
if (w / h > 1.5) {
setHeight = h;
setWidth = parseInt(280 / 220 * h);
} else if (w / h < 1) {
setWidth = w;
setHeight = parseInt(220 / 280 * w);
} else {
setHeight = h;
setWidth = w;
};
console.log(setWidth, setHeight)
ctx.drawImage(res.path, 0, 0, setWidth, setHeight, 20, 50, 280, 220);
ctx.draw(true);
},
fail: function (res) {
//失敗回撥
}
});
複製程式碼
在開發過程中如果封面圖無法按照約定的比例(280x220)給到:
那麼我們就需要處理預設封面圖過大或者過小的問題,大致思路是:程式碼中通過比較縱橫比(280/220=1.27)正比例放大或者縮小原圖,然後從左上切割,竟可能保證過高的圖是寬度100%,過寬的圖是高度100%。
在canvas中draw圖片,必須是一個(相對)本地路徑,我們可以通過將圖片儲存在本地後生成的臨時路徑。
微信官方提供兩個API:
wx.downloadFile(OBJECT)和wx.getImageInfo(OBJECT)。都需先配置download域名才能生效。
三、裁切“圓形”頭像畫圖
ctx.save(); //儲存畫圖板
ctx.beginPath()//開始建立一個路徑
ctx.arc(35, 25, 15, 0, 2 * Math.PI, false)//畫一個圓形裁剪區域
ctx.clip()//裁剪
ctx.closePath();
ctx.drawImage(headImageLocal, 20, 10, 30, 30);
ctx.draw(true);
ctx.restore()//恢復之前儲存的繪圖上下文
複製程式碼
使用圖形上下文的不帶引數的clip()方法來實現Canvas的影像裁剪功能。該方法使用路徑來對Canvas話不設定一個裁剪區域。因此,必須先建立好路徑。建立完整後,呼叫clip()方法來設定裁剪區域。
需要注意的是裁剪是對畫布進行的,裁切後的畫布不能恢復到原來的大小,也就是說畫布是越切越小的,要想保證最後仍然能在canvas最初定義的大小下繪圖需要注意save()和restore()。畫布是先裁切完了再進行繪圖。並不一定非要是圖片,路徑也可以放進去~
小程式 canvas 裁切BUG
ctx.setFillStyle("#fff");
ctx.fillRect(0, 0, 320, 500); //第一個填充矩形
wx.downloadFile({
url: headUri,
success(res) {
ctx.beginPath()
ctx.arc(50, 50, 25, 0, 2 * Math.PI)
ctx.clip()
ctx.drawImage(res.tempFilePath, 25, 25); //第二個填充圖片
ctx.draw()
ctx.restore()
ctx.setFillStyle("#fff");
ctx.fillRect(0, 0, 320, 500);
ctx.draw(true)
ctx.restore()
}
})
複製程式碼
clip裁切這個功能,如果有超過一張圖片/背景疊加,則裁切效果失效。
錯誤參考:http://html51.com/info-38753-1/
四、將canvas匯出成虛擬地址
wx.canvasToTempFilePath({
fileType: 'jpg',
canvasId: 'customCanvas',
success: (res) => {
console.log(res.tempFilePath) //為canvas的虛擬地址
}
})
res:
{
errMsg: "canvasToTempFilePath:ok",
tempFilePath: "http://tmp/wx02935bb29080a7b4.o6zAJswFAuZuKQ5NZfPr….cGnD1a02PlVC0b3284be3a41d08986c2477579a5fd8e.jpg"
}
複製程式碼
這裡需要把canvas裡面的內容,匯出成一個臨時地址才能儲存在相簿,比如: http://tmp/wx02935bb29080a7b4.o6zAJswFAuZuKQ5NZfPr5UfJVR4k.cGnD1a02PlVC0b3284be3a41d08986c2477579a5fd8e.jpg
五、詢問並獲取訪問手機本地相簿許可權
wx.getSetting({
success(res) {
console.log(res)
if (!res.authSetting['scope.writePhotosAlbum']) { //判斷許可權
wx.authorize({ //獲取許可權
scope: 'scope.writePhotosAlbum',
success() {
console.log('授權成功')
//轉化路徑
self.saveImg();
}
})
} else {
self.saveImg();
}
}
})
複製程式碼
判斷是否有訪問相簿的許可權,如果沒有,則請求許可權。
六、儲存到使用者手機本地相簿
wx.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: function (data) {
wx.showToast({
title: '儲存到系統相簿成功',
icon: 'success',
duration: 2000
})
},
fail: function (err) {
console.log(err);
if (err.errMsg === "saveImageToPhotosAlbum:fail auth deny") {
console.log("當初使用者拒絕,再次發起授權")
wx.openSetting({
success(settingdata) {
console.log(settingdata)
if (settingdata.authSetting['scope.writePhotosAlbum']) {
console.log('獲取許可權成功,給出再次點選圖片儲存到相簿的提示。')
} else {
console.log('獲取許可權失敗,給出不給許可權就無法正常使用的提示')
}
}
})
} else {
wx.showToast({
title: '儲存失敗',
icon: 'none'
});
}
},
complete(res) {
console.log(res);
}
})
複製程式碼
儲存到本地需要一定的時間,需要加一個loading的狀態。
七、關於元件中引用canvas
let ctx = wx.createCanvasContext('posterCanvas',this); //需要加this
複製程式碼
在components中canvas無法選中的問題:
在components自定義元件下,當前元件例項的this,表示在這個自定義元件下查詢擁有 canvas-id 的 <canvas> ,如果省略則不在任何自定義元件內查詢。
如果智酷君的分享能夠幫助到你,或者想持續獲得最新的全棧攻略
可以關注我的掘金號“ 智酷方程式 ”
也可在微信 搜尋“ Geek_Club ”或者“ 智酷方程式 ”
掃描二維碼關注公眾號喲?