[填坑手冊]小程序Canvas生成海報完整版

海報生成示例
海報生成示例

最近智酷君在做[小程序]canvas生成海報的項目中遇到一些棘手的問題,在網上查閱了各種資料,也踩扁了各種坑,智酷君希望把這些“填坑”經驗整理一下分享出來,避免後來的兄弟重複“掉坑”。

原型圖
原型圖

這是一個大致的原型圖,下面來看下如何製作這個海報,以及整體的思路。

原型圖
<center>海報生成流程</center>

下面分享下主要的代碼內容和“填坑現場”

一、添加字體

https://developers.weixin.qq.com/miniprogram/dev/api/canvas/font.html

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...

五、詢問並獲取訪問手機本地相冊權限

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> ,如果省略則不在任何自定義組件內查找。

分隔符

如果有什麼疑問或者糾錯可以在下面給智酷君留言。

如果智酷君的分享能夠幫助到你,或者想持續獲得最新的全棧攻略

可以在segmentfault關注我,或在VX搜索“ Geek_Club ”或者“ 智酷方程式

掃描關注公衆號👇👇👇
公衆號圖片

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章