小程式開發-利用canvas實現儲存二維碼海報到本機

LucaLJX發表於2018-09-05

小程式效果

場景及需求

在小程式開發過程中,經常需要實現儲存某個頁面為帶小程式碼的二維碼海報圖片到本地,然後用於分享或者發朋友圈等操作。

主要技術點及小程式相關api

技術注意事項

  • 小程式的canvas與H5 canvas使用api大部分一致,但由於小程式中沒有DOM節點的概念,所以不能使用很多現成的工具庫
  • 小程式canvas預設寬度300px、高度225px
  • 小程式canvas相關的api中單位為px,並非rpx,所以在業務實現過程中需要處理適配
  • 小程式canvas對跨域圖片不支援,需要先將圖片快取到本地

技術點

  • wx.getImageInfo()
  • wx.getSystemInfo()
  • wx.canvasToTempFilePath()
  • wx.saveImageToPhotosAlbum()
  • canvas渲染相關api

整體流程

1.先獲取所有圖片資源,線上圖片需要快取到本地,使用圖片的本地路徑做渲染

2.獲取裝置資訊,根據裝置寬度計算出寬度因子x

3.繪製canvas

4.將canvas轉化為圖片,將圖片儲存到本機

實現

寬度因子x及元素寬度的尺寸計算

rpx(responsive pixel): 可以根據螢幕寬度進行自適應。規定螢幕寬為750rpx。如在 iPhone6 上,螢幕寬度為375px,共有750個物理畫素,則750rpx = 375px = 750物理畫素,1rpx = 0.5px = 1物理畫素。

參考文件: 小程式-WXSS-尺寸單位

由文件可以看出不同的裝置,寬度是不同數值的px,但是均為750rpx,由此可以利用750rpx計算出寬度因子x:

裝置寬度 / 750

以設計稿寬度750rpx為例,則在不同裝置下,設計稿寬度y在不同裝置下的px寬度均為:

Y = y * x

Y = y * (裝置寬度 / 750)

由此,在實現中,canvas標籤寬高需要設定為變數,且使用微信提供的 wx.getSystemInfo() 介面獲取裝置寬度資訊後,進行計算

<canvas canvas-id="qrcode-canvas" :style="{width: canvas.width + 'px', height: canvas.height + 'px'}"></canvas>
複製程式碼
// wx.js
// 獲取裝置基本資訊
export function wxGetSystemInfo () {
  return new Promise(async function (resolve, reject) {
    wx.getSystemInfo({
      success: function (res) {
        resolve(res);
      },
      fail: function (err) {
        reject(err);
      }
    })
  });
}
// demo.vue
// 獲取手機基本資訊
async getPhoneSystemInfo () {
  let _this = this;
  let systemInfoRes = await wxGetSystemInfo();
  _this.canvas.width = systemInfoRes.screenWidth;
  // 設計稿寬高為 750 * 912
  _this.canvas.height = 912 / 750 * _this.canvas.width;
  _this.storageQrcode();
},
複製程式碼

獲取遠端圖片快取到本地使用

注意:如果是本地圖片,即'static'資料夾中的圖片,如'/static/logo.png'經過wx.getImageInfo返回的path會省去開頭的'/',即'static/logo.png',這會導致拿不到資源,所以本地圖片不需要呼叫wx.getImageInfo()進行本地快取

// wx.js
// 獲取圖片基本資訊
export function wxGetImgInfo (imgUrl) {
  return new Promise(async function (resolve, reject) {
    wx.getImageInfo({
      src: imgUrl,
      success: function (res) {
        resolve(res);
      },
      fail: function (err) {
        reject(err);
      }
    })
  });
}
// demo.vue
// 快取遠端圖片
async storageQrcode () {
  let _this = this;
  // 背景圖url轉path
  // let bgRes = await wxGetImgInfo(_this.bgImg);
  // _this.imgPath.bgImg = bgRes.path;
  // Logo url轉path
  // let logoRes = await wxGetImgInfo(_this.logo);
  // _this.imgPath.logo = logoRes.path;
  
  // 頭像 url轉path
  // let headerImgRes = await wxGetImgInfo(_this.cardDetail.header);
  // _this.imgPath.headerImg = headerImgRes.path;
  // 二維碼 url轉path
  let qrCodeRes = await wxGetImgInfo(_this.qrCode);
  _this.imgPath.qrCode = qrCodeRes.path;
  console.log(_this.imgPath);
  // _this.initCanvas(_this.canvas.width);
},
複製程式碼

繪製canvas

canvas繪製主要用到了圖片繪製、文字繪製,圖片繪製及文字繪製的時候,需要引入上文說到的寬度因子x進行計算

圖片繪製

這裡的圖片繪製之前先計算寬度因子

  let _this = this;
  // variableVal即為上文拿到的裝置寬度
  const variableNum = variableVal / 750; // 根據裝置寬度算出一個rpx為多少px
  const ctx = wx.createCanvasContext('qrcode-canvas');
  // 清除畫布上矩形的內容
  ctx.clearRect(0, 0, 0, 0);
  // 繪製上部card背景圖
  const bgImgDesc = {
    url: _this.imgPath.bgImg,
    left: 0,
    top: 0,
    width: 750 * variableNum,
    height: 912 * variableNum
  };
  ctx.drawImage(bgImgDesc.url, bgImgDesc.left, bgImgDesc.top, bgImgDesc.width, bgImgDesc.height);
  ctx.draw();
複製程式碼

文字的繪製

  • canvasContext.setFillStyle - 設定顏色
  • canvasContext.setFontSize - 設定大小
  • canvasContext.fillText - 填充文字
const nameDesc = {
  text: _this.cardDetail.name,
  fontSize: 36 * variableNum,
  color: '#4F5E6F',
  left: 102 * variableNum,
  top: 200 * variableNum
};
ctx.setFillStyle(nameDesc.color);
ctx.setFontSize(nameDesc.fontSize);
ctx.fillText(nameDesc.text, nameDesc.left, nameDesc.top);
ctx.draw();
複製程式碼

canvas轉圖片並儲存到本地

注意點

tip: wx.canvasToTempFilePath() 在 draw 回撥裡呼叫該方法才能保證圖片匯出成功。

由於匯出圖片需要在canvas繪製圖片的draw()方法回撥中使用才能,所以我們在繪製canvas的時候直接轉canvas為圖片,然後將路徑存下來,點選下載的時候,再直接拿圖片路徑進行下載操作。

// wx.js
// canvas畫布轉圖片
export function wxCanvasToTempFilePath (canvasObj) {
  return new Promise(async function (resolve, reject) {
    wx.canvasToTempFilePath({
      x: canvasObj.x,
      y: canvasObj.y,
      width: canvasObj.width,
      height: canvasObj.height,
      destWidth: canvasObj.destWidth,
      destHeight: canvasObj.destHeight,
      canvasId: canvasObj.canvasId,
      fileType: canvasObj.fileType ? canvasObj.fileType : 'png',
      success: function (res) {
        resolve(res);
      },
      fail: function (err) {
        reject(err);
      }
    })
  });
}
// demo.vue
// 繪製canvas
initCanvas () {
  let _this = this;
  // 繪製canvas
  ......
  ctx.draw(false, function () {
    _this.saveImg();
  });
},
// 將canvas轉為圖片
async saveImg () {
  let _this = this;
  const canvasObj = {
    x: 0,
    y: 0,
    width: _this.canvas.width,
    height: _this.canvas.height,
    destWidth: _this.canvas.width * 4,
    destHeight: _this.canvas.height * 4,
    canvasId: 'qrcode-canvas',
    fileType: 'png'
  };
  let imgRes = await wxCanvasToTempFilePath(canvasObj);
  _this.qrCodeImgPath = imgRes.tempFilePath;
},
複製程式碼

下載圖片到本地

<canvas canvas-id="qrcode-canvas" :style="{width: canvas.width + 'px', height: canvas.height + 'px'}"></canvas>
<button type="primary" plain="true" @click="downLoadImg"> 儲存二維碼 </button>
複製程式碼
// wx.js
// 儲存圖片到本地
export function wxSaveImageToPhotosAlbum (filePath) {
  return new Promise(async function (resolve, reject) {
    wx.saveImageToPhotosAlbum({
      filePath: filePath,
      success: function (res) {
        
        resolve(res);
      },
      fail: function (err) {
        reject(err);
      }
    })
  });
}
// demo.vue
// 儲存圖片到本機
async downLoadImg () {
  let _this = this;
  let saveRes = await wxSaveImageToPhotosAlbum(_this.qrCodeImgPath);
  if (saveRes.errMsg === 'saveImageToPhotosAlbum:ok') {
    wx.showToast({
      duration: 3000,
      icon: 'none',
      title: '儲存圖片成功!',
      mask: true
    });
  } else {
    wx.showToast({
      duration: 3000,
      icon: 'none',
      title: '儲存圖片失敗,請重試!',
      mask: true
    });
  }
},
複製程式碼

demo程式碼已經放在 './demo' 資料夾,歡迎交流

總結

在業務實現中,我們只要把業務流程進行分割,然後一步一步去實現,捋明白流程之後各個擊破很多第一反應去查詢已有的庫來實現的功能自己實現起來也很簡單。

在某種意義上,自己弄明白原理之後去實現反而更加輕鬆,更加得心應手。

-- LucaLJX: github:https://github.com/LucaLJX

相關文章