小程式的填坑小技巧之Canvas

dduke發表於2019-03-03

前言

小程式分享只能分享到好友或者微信群裡,不能分享到朋友圈。

這周接到一個這樣的坑爹需求,小程式需要實現一個圖片分享的功能。讓使用者可以把圖片傳送到朋友圈或者其他渠道。

小程式的填坑小技巧之Canvas

剛開始拿到這個需求,覺得還好,沒有太大的感覺, 第一個感覺想把鍋甩給後端,呼叫一個介面讓後端傳回來一個URL,前端只負責顯示就好。^_^

當然這麼天真的想法基本上不可能實現的,如果你遇到這樣的後端,請好好的珍惜他。

言歸正傳,前端自己做這個功能的話,必須要藉助一個東西,那就是 Canvas。

初用

Canvas 並不是小程式獨有的,html5裡也有。不過本人實在是用的不多。唯二兩次 其一做h5小遊戲的時候 另一次跟本次的需求很接近,也是把一個 div 裡的內容放入 canvas 並生成圖片 讓使用者可以保持。

我就按照之前的思路來做是沒問題的,小程式自己封裝了一套API, 不過 canvas 相關的 API 跟正常 html 的基本一樣, 我就按照以前 web 的方式再過一遍。

小程式的填坑小技巧之Canvas

開用

首先在我們的wxml寫下如下程式碼

<canvas class="canvas" canvas-id="canvas"></canvas>
複製程式碼

設定 Canvas 大小,可以直接在 wxml 裡的 canvas 標籤上設定,也可以用 wcss 。個人比較喜歡用wcss 。

重點來了 canvas-id 是小程式裡特有的,是canvas 元件的唯一識別符號,之後的很多地方都需要用到。

然後我們就開始寫 js 了

// 這裡的 canvas 就是之前在 wxml 裡的 canvas-id
let ctx = wx.createCanvasContext(`canvas`)
複製程式碼

第一個坑的地方來了,正常來 page 裡用是一點沒問題沒有的,但是在元件裡的話,畫不了,就是顯示不出來。

文件能發現下列一段話

建立 canvas 繪圖上下文(指定 canvasId)。在自定義元件下,第二個引數傳入元件例項this,以操作元件內 <canvas/> 元件Tip: 需要指定 canvasId,該繪圖上下文只作用於對應的 <canvas/>

很顯然,我們就程式碼改成如下即可

let ctx = wx.createCanvasContext(`canvas`, this)
複製程式碼

在相應的位置畫文字及圖片

具體怎麼畫,我就不講了,直接開始講坑的點。

在相應的位置

沒錯,就一個很坑的點。canvas 是居中的,而且在不同尺寸的螢幕上各個點的位置都不一樣。我們需要獲取螢幕的寬度以及高度進行計算。(主要是寬度)
小程式提供瞭如下方面,讓我們來獲取裝置的一些資訊

wx.getSystemInfo({
  success: (res) => {
    this.setData({
      windowWidth: res.windowWidth,
      windowHeight: res.windowHeight,
    })
  }
})
複製程式碼

文字

這個是最坑的一個點

看UI給的樣式,中間會有一段兩行的文字。

開始拿到的時候,覺得so easy。算了每一行的字數,擷取兩段就不好了嘛。 最後出來的效果確實很坑爹。有長有短,還有超過界限的。

這樣明顯不能滿足我們的需求。而之所以會造成這樣的問題。就是因為我們的內容是中英文混合的,中文佔兩個字元長度而英文只佔一個,我們根據字串的length擷取時,中英文都是1。
除非是純英文或者純中文,才能對齊。怎麼解決呢? 上程式碼!

getContent(detail, length = 24, row = 2) {
  let len = 0
  let index = 0
  let content = []
  for (let i = 0; len < detail.length; i++) {
    // 若未定義則致為 ``
    if (!content[index]) content[index] = ``
    content[index] += detail[i]
    // 中文或者數字佔兩個長度
    if (detail.charCodeAt(i) > 127 || (detail.charCodeAt(i) >= 48 && detail.charCodeAt(i) <= 57)) {
      len += 2;
    } else {
      len++;
    }
    if (len >= length || (row - index == 1 && len >= length - 2)) {
      len = 0
      index++
    }
    if (index === row) break
  }
  return content
}
複製程式碼

我們可以使用 charCodeAt 來獲取 charCode,中文的話在 127以上(ps.測試的時候發現 數字也是佔兩個長度的)。
為了預防之後需求的變動 我把這塊提取出來寫了一個方法。之後只用傳入內容,每行的字數以及行數就好。

圖片

這裡有兩個坑

  1. 不能直接使用網路圖片,在真機上畫不出來
  2. 不能圓角,圖片如果是方形的 不會再處理

網路圖片使用

既然不能直接使用網路圖片,那麼我們換個思路,把圖片下載到本地,在顯示出來是不是就行了。

  1. 下載網路圖片
wx.getImageInfo({
  src: url,
  success: (res) => {
    // 下載成功 即可獲取到本地路徑
    console.log(res.path)
  }
})
複製程式碼

直接用這個你會發現,會出錯。小程式對能下載的圖片做白名單處理。即需要在 小程式後臺 > 設定 > 伺服器域名 > downloadFile合法域名 裡設定網路圖片的域名。

ps.因為域名要求是https的, 並且一個月只能修改五次,建議把需要下載的網路圖片放在自己的https的伺服器上,再走個CDN什麼的。

  1. 繪製圖片

接下來跟繪製本地圖片一樣

ctx.drawImage(res.path, left, top, width, height);
複製程式碼

圖片圓角處理 (本例示範的是圓形,原理一樣)

整體思路是畫一個圓形區域,再在這塊區域把圖片畫上去,最後進行合併擷取就生成了圓形圖片。具體程式碼如下所示

drawImage(ctx, url, left, top, width, height) {
  // 儲存當前環境的狀態
  ctx.save();
  // 起始一條路徑,或重置當前路徑
  ctx.beginPath();
  // 畫一個圓
  ctx.arc(width / 2 + left, height / 2 + top, width / 2, 0, Math.PI * 2, false);
  // 從原始畫布剪下任意形狀和尺寸的區域
  ctx.clip();
  wx.getImageInfo({
    src: url,
    success: (res) => {
      // 向畫布上繪製影像
      ctx.drawImage(res.path, left, top, width, height);
      // 返回之前儲存過的路徑狀態和屬性
      ctx.restore();
      // 畫出來
      ctx.draw();
    }
  })
},
複製程式碼

會發現下載圖片是一步非同步操作,如果只是一個圖片還好,如果是多個的話,就會非常的亂,我們可以用 Promise 優化下

drawImage(ctx, url, left, top, width, height) {
  ctx.save();
  ctx.beginPath();
  ctx.arc(width / 2 + left, height / 2 + top, width / 2, 0, Math.PI * 2, false);
  ctx.clip();
  return new Promise((resolve, reject) => {
    wx.getImageInfo({
      src: url,
      success: (res) => {
        ctx.drawImage(res.path, left, top, width, height);
        ctx.restore();
        resolve()
      },
      fail: (e) => {
        reject(e)
      }
    })
  })
},
複製程式碼

把當前畫布指定區域的內容匯出生成指定大小的圖片,並返回檔案路徑。

使用微信提供的canvasToTempFilePath

savePic () {
  let that = this;
  let offset_left = (this.data.windowWidth - 303) / 2
  console.log(`savePic`)
  wx.canvasToTempFilePath({
    x: offset_left,
    y: 0,
    width: 303,
    height: 398,
    canvasId: `canvas`,
    success: function (res) {
      console.log(res.tempFilePath)
    },
    fail (e) {
      console.log(e)
    }
  }, this)
}
複製程式碼

預覽/儲存圖片

上一步我們已經把把canvas的內容儲存生成圖片了。繼續來其實有兩種方案。

  1. saveImageToPhotosAlbum, 把該檔案儲存到相簿裡去
  2. previewImage,直接預覽該圖片

其實兩種方法都行,可以根據實際的需求做相應的選擇。

我在選擇的時候採用了 previewImage,因為這個不需要使用者授權, 使用者可以直接把這個圖片傳送給朋友 並不需要儲存圖片。如果想要儲存圖片的話,在預覽的時候 也可以儲存。

小感

以前覺得看文章覺得也就那麼回事,自己寫的時候才發現,是多麼的難產。也許還比較菜吧,繼續努力。這是我在掘金上釋出的第三篇文章

前兩篇分別如下,有需要的話 可以看看

小程式的填坑小技巧之網路請求改造
移動端適配問題解決方案

最近在寫 DApp, 基於 nebulas 公鏈的。5月到7月兩個月時間有一個激勵計劃,上架一個DApp 即可獲得 110 NAS 約等於 6000 RMB,周大獎約為100w RMB,不過這個基本上不考慮了, 但是上架一個 DApp 也算一個不小的收入。有興趣的小夥伴可以去註冊下。註冊連結

剛開始一週, 本人提交了兩個DApp, 遇到了一些坑吧 在本週會寫一個新手教程出來,幫助大家更好的開發DApp

相關文章