Stories

Detail Return Return

手把手教你繪製小程序海報 - Stories Detail

海報分享功能在許多應用中應該是很常見的,因為它作為一種常用的應用推廣和拉新的方式。

接下來看個實際的案例,如下:
在這裏插入圖片描述

把任務拆解下:

  • 如何繪製海報
  • 如何把繪製後的海報保存到相冊

繪製海報

canvas 來繪製海報。 這裏需要了解基本的 canvas api,不熟悉可以先去了解下相關 Canvas API

  • 定義 canvas 元素
<template>
  <view class="poster-container">
    <canvas class="poster" canvas-id="posterId"></canvas>
    <button class="btn" @click="onSave">保存至相冊</button>
  </view>
</template>
  • 獲取 canvas 上下文對象
const context = uni.createCanvasContext('posterId');
  • 繪製背景圖片

圖片支持遠程圖片本地圖片,網絡圖片要通過 getImageInfo / downloadFile 先下載。

context.drawImage('/static/poster.png', 0, 0, 320, 410); // 繪製背景圖片
  • 繪製頭像和暱稱
    在這裏插入圖片描述
const avatarLeft = 185;
const avatarTop = 18;
const avatarWidth = 16;
const avatarHeight = 16;
context.drawImage(
  '/static/avatar.png',
  avatarLeft,
  avatarTop,
  avatarWidth,
  avatarHeight
);

從設計上來看頭像和暱稱是對齊的。
在這裏插入圖片描述

直接按照設計稿距離來設定看看效果

const nickName = '牆頭草中的頂尖的';
const nameLeft = avatarLeft + avatarWidth + 7;
context.setFillStyle('#ffffff');
context.setFontSize(12);
context.fillText(nickName, nameLeft, 21, 96);

實際效果如下:
在這裏插入圖片描述

發現與預期效果對應不上,思考下這是為什麼? 文字設置距離偏移的參考基線不是文字的頂部。

怎麼驗證我的説法呢?可以 x 值設置為 0

const nickName = '牆頭草中的頂尖的';
const nameLeft = avatarLeft + avatarWidth + 7;
context.setFillStyle('#ffffff');
context.setFontSize(12);
context.fillText(nickName, nameLeft, 0, 96); // 修改為 0

再看看效果:
在這裏插入圖片描述

發現參考點幾乎是文字的底部,這就驗證了上面的結論。

怎麼解決這個問題呢?

  • 獲取文本行的高度
  • 更改偏移的參考點

通常知道文本行實際佔用的高度,需要知道其 line-height,上面並不知道其 line-height 值。canvas 本身並不直接支持line-height屬性,顯然沒辦法獲得相對準確的行高。

只能採用第二種方式,從 canvas 提供 API 來看,可以更改文本相行對齊的點。

const nickName = '牆頭草中的頂尖的';
const nameLeft = avatarLeft + avatarWidth + 7;
context.setFillStyle('#ffffff');
context.setFontSize(12);
context.setTextBaseline('top'); // 更改基線對齊點
context.fillText(nickName, nameLeft, 21, 96);

再來看看效果如下:
在這裏插入圖片描述

接下來實現二維碼區域,主要白色背景 + 二維碼 + 文案

  • 白色背景區域
const rectWidth = 300;
const rectHeight = 89;
const rectLeft = 10;
const rectTop = 311;
context.setFillStyle('#ffffff');
context.fillRect(rectLeft, rectTop, rectWidth, rectHeight);

效果如下:

在這裏插入圖片描述

  • 繪製二維碼
const qrcodeLeft = 20;
const qrcodeTop = rectTop + 10;
const qrcodeWidth = 68;
const qrcodeHeight = 68;
context.drawImage(
  '/static/qrcode.png',
  qrcodeLeft,
  qrcodeTop,
  qrcodeWidth,
  qrcodeHeight
);

二維碼這裏直接採用現成圖片。實際上前端可以通過 weapp.qrcode.esm.js 在前端生成二維碼,再把它繪製上去。

  • 繪製多行文本
const startX = qrcodeLeft + qrcodeWidth + 15;
const text1 = '與志同道合的,他們一起成長';
context.setFillStyle('#161413');
context.setFontSize(14);
context.setTextBaseline('top');
context.fillText(text1, startX, rectTop + 29);
const text2 = '掃碼即可進入“Get一下”社區';
context.font = 'bold 14px Arial';
context.fillText(text2, startX, rectTop + 51);

看看最後的效果:

在這裏插入圖片描述

左上角的部分沒有繪製,思路同頭像和暱稱一樣。 接下來只要把圖片保存到相冊即可。

保存到相冊

在小程序中,提供方法支持如下:

const onSave = () => {
  // 轉換為臨時路徑
  uni.canvasToTempFilePath({
    canvasId: 'posterId',
    success: (res) => {
      // 保存圖片到相冊
      uni.saveImageToPhotosAlbum({
        filePath: res.tempFilePath,
        success: () => {
          uni.showToast({
            title: '保存成功',
            icon: 'none',
          });
        },
        fail: () => {
          uni.showToast({
            title: '保存失敗',
            icon: 'none',
          });
        },
      });
    },
  });
};

這裏基本上把一個海報繪製

擴展

  • 在繪製名稱時,用户名稱長短不一至,如果名字過長時會出現什麼效果呢
const nickName = '牆頭草中的頂尖的牆頭草中的頂尖的';
const nameLeft = avatarLeft + avatarWidth + 7;
context.setFillStyle('#ffffff');
context.setFontSize(12);
context.fillText(nickName, nameLeft, 21, 96);

在這裏插入圖片描述

從效果來看,發現文字直接重疊。如果希望超出的部分能夠通過省略號來省略,是可以的

const nickName = '牆頭草中的頂尖的牆頭草中的頂尖的';
const nameLeft = avatarLeft + avatarWidth + 7;
context.setFillStyle('#ffffff');
context.setFontSize(12);
context.fillText(nickName, nameLeft, 21, 96);

let text = '';
const textArr = nickName.split('');
const ellipsisWidth = context.measureText('...').width; // 省略號的寬度
for (let i = 0; i < textArr.length; i++) {
  const temp = text + textArr[i];
  const metrics = context.measureText(temp);
  if (metrics.width + ellipsisWidth > 96) {
    text = text + '...';
    break;
  }
  text = temp;
}
context.fillText(text, nameLeft, 21, 96);

上面主要是通過 measureText 方法獲得文字對應寬度,針對超出的部分採用省略號替代。當然,如果希望完整地顯示名字,也可以使用換行的方式。具體的實現方式,就留給大家自己思考和實現了。

處理後效果如下:
在這裏插入圖片描述

  • 頭像實現圓角

默認採用圓角頭像,如果用户上傳的頭像沒有進行裁剪處理,導致圖片出現非圓角情況,那麼在海報上呈現的效果可能會有所差異。

先使用一個非圓角的圖片,代碼修改如下:

const avatarLeft = 185;
const avatarTop = 18;
const avatarWidth = 16;
const avatarHeight = 16;
context.drawImage(
  '/static/avatar-rect.png',
  avatarLeft,
  avatarTop,
  avatarWidth,
  avatarHeight
);

效果如下:
在這裏插入圖片描述

現在對圖片處理一下:

const avatarLeft = 185;
const avatarTop = 18;
const avatarWidth = 16;
const avatarHeight = 16;
context.beginPath();
context.arc(
  avatarLeft + avatarWidth / 2,
  avatarTop + avatarHeight / 2,
  avatarWidth / 2,
  0,
  2 * Math.PI
);
context.closePath();
context.clip();
// 繪製圓形頭像
context.drawImage(
  '/static/avatar-rect.png',
  avatarLeft,
  avatarTop,
  avatarWidth,
  avatarHeight
);

效果如下:
在這裏插入圖片描述

圓角是實現了,發現其他區域內容都被裁剪了。 這是為什麼? clip()改變了繪畫環境。ctx()調用後,所裁剪的區域就是 clip ()後的繪製環境,clip()之後的繪畫只能在裁剪區域中渲染,不能訪問畫布上的其他區域。

怎麼處理這個問題呢 ? 通過 save()restore().

context.save(); // 暫存
context.beginPath();
context.arc(
  avatarLeft + avatarWidth / 2,
  avatarTop + avatarHeight / 2,
  avatarWidth / 2,
  0,
  2 * Math.PI
);
context.closePath();
context.clip();
// 繪製圓形頭像
context.drawImage(
  '/static/avatar-rect.png',
  avatarLeft,
  avatarTop,
  avatarWidth,
  avatarHeight
);
context.restore(); // 恢復

效果如下:
在這裏插入圖片描述

總結

  • 通過 canvas + 小程序提供 API 實現海報繪製、保存海報到相冊
  • 優化特殊情況下名稱和頭像的展示方式。

如果您有任何疑問,請隨時在評論區留言。

user avatar tianmiaogongzuoshi_5ca47d59bef41 Avatar toopoo Avatar cyzf Avatar Leesz Avatar alibabawenyujishu Avatar haoqidewukong Avatar zaotalk Avatar linlinma Avatar yinzhixiaxue Avatar freeman_tian Avatar jingdongkeji Avatar dirackeeko Avatar
Favorites 260 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.