[技術博客]海報圖片生成——小程序canvas畫布

背景介紹

目標:利用canvas畫布生成社團活動的海報圖片,便於社團活動宣傳以及對小程序的推廣。html

場景:用戶A以爲某個社團的活動很不錯,所以點擊「分享」按鈕,生成一個該活動的海報圖片,接着,用戶A把該圖片發到某個羣或者朋友圈進行傳播,用戶B看到該圖片後對這個活動蠻感興趣,經過長按識別圖片中的小程序碼,可以進入「北航社團幫」小程序的相應活動詳情頁。java

UI設計:咱們將海報內容需求進行了詳細描述後,聯繫了社聯宣傳部幫忙製做了海報原型的ps圖,即本人將按照這個目標去生成海報:canvas

(附:社聯設計的長寬比不大合適,過長,本人將進行調整)小程序

canvas簡介

代碼實現

首先須要在wxml中使用canvas組件,注意須要把組件位置設置到屏幕以外,由於canvas畫布畫出來很醜。(雖然保存爲圖片的時候很好看)微信

<view>
  <canvas style="width: 375px;height: 690px;position:fixed;top:9999px" canvas-id="mycanvas" />
  <!-- canvas畫布畫出來很醜,不能用讓用戶看見 -->
</view>

接着須要在js中編寫canvas繪製函數ide

//在canvas上進行具體繪畫的函數
  drawCanvas: function () {
    console.log("開始draw convas")
    var context = wx.createCanvasContext('mycanvas');
    var greycolor = '#969696';
    var maincolor = '#eda874';

    //0.繪製背景圖片和原豎版海報
    console.log("在畫海報時,原海報下載的臨時地址爲:")
    console.log(_this.data.poster_old)
    context.drawImage(_this.data.poster_old, 69, 120, 237, 333);
    context.drawImage("/images/bg4.png", 0, 0, 375, 690);

    context.save();
    //1.繪製頭像
    var radius = 20;
    var center_x = 79;
    var center_y = 30;
    context.arc(center_x, center_y, radius, 0, 2 * Math.PI) //畫出圓
    context.clip(); //裁剪上面的圓形
    console.log("在畫海報時,原頭像下載的臨時地址爲:")
    console.log(_this.data.touxiang)
    context.drawImage(_this.data.touxiang, center_x - radius, center_y - radius, radius * 2, radius * 2); // 在剛剛裁剪的園上畫圖
    context.restore();

    //2.繪製暱稱
    console.log(_this.data.userInfo)
    var nickname = _this.data.userInfo.nickname.replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&apos;/g, "'").replace(/&ensp;/g, " ").replace(/&emsp;/g, " ") //特殊字符轉義
    // nickname = "分解分解紅紅火火恍恍惚惚a";
    var max_nickname_width = 120;//一個漢字的寬度爲10
    context.font = 'normal 10px sans-serif';
    var nickname_len_by_10 = context.measureText(nickname).width;
    if (nickname_len_by_10 > max_nickname_width) {//暱稱寬度大於最大顯示寬度,則只取前8個字符
      console.log("暱稱太長,cut");
      nickname = nickname.slice(0, 12) + '...';
    }
    var width = 17;//暱稱的字號
    var position_x_name = 60;
    var position_y_name = 75;
    context.setFontSize(width);
    context.setTextAlign('left');
    context.setFillStyle(greycolor);
    context.fillText(nickname, position_x_name, position_y_name);

    //3.繪製活動標題
    var actname = _this.data.activity_dict.name;
    // actname = "分解分解紅紅火火恍恍惚惚什麼什麼分解分解紅紅火火恍恍惚惚什麼什麼";
    var max_actname_width = 130;
    context.font = 'normal 10px sans-serif';
    var actname_len_by_10 = context.measureText(actname).width;
    if (actname_len_by_10 > max_actname_width) {//暱稱寬度大於最大顯示寬度,則只取前8個字符
      console.log("活動名稱太長,cut");
      actname = actname.slice(0, 13) + '...';
    }
    var width = 19;//活動名稱的字號
    var position_x_actname = 62;
    var position_y_actname = 495;
    context.font = 'normal bold 10px sans-serif';
    context.setFontSize(width);
    context.setTextAlign('left');
    context.setFillStyle(maincolor);
    context.fillText(actname, position_x_actname, position_y_actname);

    //4.繪製活動社團名稱、活動地點
    //4.1.關於內容
    var club_const = "社團名稱";
    var place_const = "活動地點";
    var club = _this.data.club_info.name;
    var place = _this.data.activity_dict.place;
    var max_width = 130;//你體會一下
    context.font = 'normal 10px sans-serif';
    // club = "解分解紅紅火火恍恍惚惚什麼什麼解分解紅紅火火恍恍惚惚什麼什麼";
    // place = "分解分解紅紅火火恍恍惚惚什";
    var club_by_10 = context.measureText(club).width;
    if (club_by_10 > max_width) {//寬度大於最大顯示寬度,則只取前8個字符
      console.log("社團名稱太長,cut");
      club = club.slice(0, 12) + '...';
    }
    var place_by_10 = context.measureText(place).width;
    if (place_by_10 > max_width) {//寬度大於最大顯示寬度,則只取前8個字符
      console.log("地點太長,cut");
      place = place.slice(0, 12) + '...';
    }
    //4.2.關於樣式
    var start_y = 527;
    var x = 64;
    var dis = 20;//"行間距"
    //4.3.畫它
    context.setTextAlign('left');
    context.font = 'normal 12px sans-serif';
    context.setFillStyle(maincolor);
    context.fillText(club_const, x, start_y);
    context.setFillStyle(greycolor);
    context.fillText(club, x, start_y + dis);
    context.setFillStyle(maincolor);
    context.fillText(place_const, x, start_y + dis * 2);
    context.setFillStyle(greycolor);
    context.fillText(place, x, start_y + dis * 3);

    //5.繪製活動時間相關信息
    //5.1.關於內容
    var start_time = _this.data.activity_dict.start_time;//形如"2019-05-26 18:00 週日"
    var year = start_time.slice(0, 4);
    var month = start_time.slice(5, 7);
    var day = start_time.slice(8, 10);
    var hour_min_start = start_time.slice(11, 16);
    var hour_min_end = "(´-ω-`)";// (´-ω-`)   o(≧v≦)o  (≧v≦)
    var end_time = _this.data.activity_dict.end_time;
    var act_in_one_day = (start_time.slice(0, 10) == end_time.slice(0, 10));//判斷該活動是否在一天內結束
    console.log("是否在一天內結束活動:")
    console.log(act_in_one_day);
    //僅當且僅當該活動end_time不爲空,且該活動在一天內結束時,才顯示活動結束時間,不然顯示顏表情
    if (end_time != "" && act_in_one_day) {
      hour_min_end = end_time.slice(11, 16);
    }
    //5.2.畫它
    // context.setFillStyle(maincolor);//爲何要在下面每一個地方都放一個,由於真機第一次生成時有點問題
    context.setTextAlign('left');
    context.setFillStyle(maincolor);
    context.font = 'normal bold 25px sans-serif';//這個字體還行,不換了...
    context.fillText(month, 238, 548);
    context.setFillStyle(maincolor);
    context.font = 'normal bold 25px sans-serif';
    context.fillText(day, 238, 589);
    context.setFillStyle(maincolor);
    context.setTextAlign('right');
    context.font = 'normal 11px sans-serif';
    context.fillText(year, 316, 516);
    context.setFillStyle(maincolor);
    context.setTextAlign('right');
    context.font = 'normal 14px sans-serif';
    context.fillText(hour_min_start, 316, 556);
    context.setFillStyle(maincolor);
    context.setTextAlign('right');
    context.font = 'normal 14px sans-serif';
    context.fillText(hour_min_end, 316, 585);

    //6.繪製小程序碼
    context.drawImage(_this.data.minicode, 255, 600, 60, 60);

    //7.將canvas生成好的圖片下載到臨時文件夾
    console.log("畫好了")
    context.draw(false, function () {
      wx.canvasToTempFilePath({
        canvasId: 'mycanvas',
        success: function (res) {
          var tempFilePath = res.tempFilePath;
          _this.setData({
            imagePath: tempFilePath,
            hide_poster: false
          });
          console.log("圖片下載到臨時文件夾了")
        },
        fail: function (res) {
          console.log(res);
        }
      });
    });
  }

其中涉及到許多小程序中canvas畫布的接口,讀者請自行在微信官方文檔中查看。比較重要的幾點將在下文說明。函數

下面先展現一下代碼中所用到的固定圖片"/images/bg4.png",是本人利用PS生成的,是png圖片,中間給海報圖片留了空:(論善用PS的重要性)post

下面展現下最後的海報生成效果:測試

點擊活動詳情頁面的「分享」按鈕:

就會生成活動的海報:

點擊「保存相冊」,便可將圖片保存到相冊。保存到相冊的圖片以下:

難點講解

圓角矩形裁剪失敗之PS的妙用

  • 第0步,繪製背景圖片和原豎版海報時,善用了PS,在本地圖片bg4.png中放置豎版海報的地方,裁出一個透明的圓角矩形。
  • 這麼作的目的主要是,若是先畫背景圖片再畫海報的話,因爲海報須要實現圓角矩形,所以須要先經過canvas繪製路徑並裁剪後再drawImage,我一開始確實是這麼作的,而且參考了這篇博客寫出了下面的代碼:
//3.繪製原豎版海報
    //3.1.繪製圓角矩形。首先定義圓角矩形的左上角點座標,圓的半徑,矩形的長寬。
    var x = 70;
    var y = 122;
    var r = 8;
    var w = 235;
    var h = 329;
    context.beginPath()
    // 由於邊緣描邊存在鋸齒,最好指定使用 transparent 填充
    context.setFillStyle('transparent');// 這裏是使用 fill 仍是 stroke均可以,二選一便可
    // 左上角,border-top
    context.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5)
    context.moveTo(x + r, y)
    context.lineTo(x + w - r, y)
    context.lineTo(x + w, y + r)
    // 右上角,border-right
    context.arc(x + w - r, y + r, r, Math.PI * 1.5, Math.PI * 2)
    context.lineTo(x + w, y + h - r)
    context.lineTo(x + w - r, y + h)
    // 右下角,border-bottom
    context.arc(x + w - r, y + h - r, r, 0, Math.PI * 0.5)
    context.lineTo(x + r, y + h)
    context.lineTo(x, y + h - r)
    // 左下角,border-left
    context.arc(x + r, y + h - r, r, Math.PI * 0.5, Math.PI)
    context.lineTo(x, y + r)
    context.lineTo(x + r, y)
    // 這裏是使用 fill 仍是 stroke均可以,二選一便可,可是須要與上面對應
    context.fill()
    context.closePath() //關閉路徑
    //3.2.裁剪圓角矩形,繪製豎版海報
    context.clip(); //裁剪上面的圓角矩形
    console.log("在畫海報時,原海報下載的臨時地址爲:")
    console.log(_this.data.poster_old)
    context.drawImage(_this.data.poster_old, x, y, w, h); // 在剛剛裁剪的園上畫圖
    context.restore();

最後在模擬器上確實實現了想要的效果,可是真機調試時,卻發現沒法看到豎版海報,只能看到圓角矩形。通過了堅苦卓絕的debug,仍然沒有找到緣由(沒有試出緣由),抱着結果比過程更重要的決心,我使用PS奇淫巧技地實現了想要的效果。

也就是:先將豎版海報圖片放在底層,而後用頂層圖片進行覆蓋,頂層圖片是經過PS留下中間透明的圓角矩形,從而實現對豎版海報圖片的圓角矩形裁剪效果。

編碼不要過硬

canvas繪圖許多地方須要定位,若是將全部位置都進行硬編碼的話,不管是在開發仍是後期調整的過程當中,都會難以調整,對此,筆者倡導適當進行硬編碼,一旦你發現某個位置的座標須要使用計算器計算/心算時,就代表你差很少應該使用變量來進行軟編碼的。

對過長的文字進行截取

以對過長的暱稱進行截取爲例:

var max_nickname_width = 120;//一個漢字的寬度爲10
    context.font = 'normal 10px sans-serif';
    var nickname_len_by_10 = context.measureText(nickname).width;
    if (nickname_len_by_10 > max_nickname_width) {//暱稱寬度大於最大顯示寬度,則只取前8個字符
      console.log("暱稱太長,cut");
      nickname = nickname.slice(0, 12) + '...';
    }

首先,設置字體大小爲10px,而後使用measureText函數(具體自行查看文檔),便可獲得所測量文字在10px字體下的寬度(一個漢字的寬度是10px,字母和標點符號會小一些)。

接着,設置max_width爲120,代表暱稱最長應是120px,也就是12個漢字的寬度,若是暱稱過長將會進行截取,截取是經過slice函數進行。

真機首次生成時字體不對

在真機測試時,發現首次生成的海報的字號不對,解決方案是:

在繪製文字filltext前,都設置一下文字的樣式(大小、顏色、字體等)。

drawImage只能使用本地圖片

這點將在下一篇技術博客進行詳解:http://www.javashuo.com/article/p-hydjefpt-gq.html

相關文章
相關標籤/搜索