小程式的canvas繪圖的封裝

weixin_34146805發表於2018-09-17

背景:由於小程式沒有直接分享到朋友圈的介面,但一些日常操作又需要用到分享到朋友圈等。於是,
只能採取“曲線救國”的路線,生成一張精美的卡片等,由使用者分享朋友圈。

1.關於canvas的一些坑

  • 1.canvas元件在hidden情況下依然可以執行繪製操作,並且不佔據空間。
  • 2.canvas元件在hidden情況下需要指定width和height,在hidden情況下,無法對canvas的width和height進行動態調整
  • 3.要想動態調整canvas的width和height,需要使用wx:if="{{}}"來配合。 // 缺點是,繪製的時候canvas是顯示的
    注:也就是使用控制條件,平時不顯示,在需要繪製的時候才顯示進行繪製圖片(可以放到頁面底部)

2.canvas的封裝

canvas繪製分享圖片的操作可能在一個專案中要用上好幾次,或者很多專案都需要用到。這時候我們就可以把它封裝
起來,到用的時候直接呼叫。也省的每次都編寫重複的程式碼,還可能出錯。

2.1 制定目標

我們對canvas相關操作進行封裝,其實是為了生成精美的分享圖片(其中可能涉及到文字和圖片的繪製)。
所以,目標我們有了。基於這個目標,我們大概需要如下屬性。

2.2 敲定引數

  • 1.canvasId // string型別
  • 2.offsetx // number型別。也就是wx.canvasToTempFile()中的x,這個x不一定是0,所以我們設為這個引數名。
  • 3.offsety // number型別。同上
  • 4.width // number型別。wx.canvasToTempFile()中的width
  • 5.height // number型別。wx.canvasToTempFile()中的height
  • 6.destWidth // number型別。wx.canvasToTempFile()中的destWidth
  • 7.destHeight //number型別。wx.canvasToTempFile()中的destHeight

因為要繪製圖片或者文字。所以,除此之外,我們還需要,imgList和wordsList

  • 8.imgList // array型別
  • 9.wordsList // array型別

imgList中,每個資料都應是1個需繪製圖片的所有資訊,所以它應該是json資料格式的,即{},wordsList同理
那麼imgList就需要如下資料且都是必須傳入的(如果傳入圖片的話)

  • 8.imgList // array型別
    • 1.source // 圖片地址
    • 2.x // 繪製的起點位置的x軸
    • 3.y // 繪製的起點位置的y軸
    • 4.width // 繪製的width
    • 5.height // 繪製的height

同理,wordsList就需要如下資料,

  • 9.wordsList // array型別
    • 1.word // 即需要繪製的問題
    • 2.x // 繪製的位置點x
    • 3.y // 繪製的位置點y
    • 同時可能還需要設定其他資訊,如下(非必須)
    • 4.font // 要繪製的文字大小size,用於setFontSize()
    • 5.textAlign // 文字的水平對齊方式,僅為 'left' 'center' 'right'
    • 6.textBaseLine // 文字的縱向對齊方式,僅為 'top' 'bottom' 'middle' 'normal'
    • 7.textColor // 文字的顏色

由於wordsList中存在非必須傳入的一些屬性,當不傳入時,那麼就可能會出現一些問題。於是設定一些
可以複用的引數,接著前面的wordsList下來

  • 9.wordsList // array 型別
  • 10.font // 全域性的font,不傳入時,預設為35
  • 11.textAlign // 全域性的textAlign ,不傳入時,預設為center
  • 12.textBaseLine // 全域性的textBaseLin,不傳入時,預設為middle
  • 13.textColor // 全域性的textColor,不傳入時,預設為 black

至此,所需要的引數,基本敲定。但我們不可能在呼叫方法的時候傳入十幾個引數。所以,我們就把這些引數,
用{}包起來(也就是接收一個{}格式的引數),呼叫的時候把所有資料用{}包起來傳進來即可。

2.3函式封裝

新建js檔案,開始封裝函式

// data為{}格式資料,dosomething為回撥函式
function gk_draw(data,dosomething){
    
}

新建驗證函式,總共有3個。

  • 1.分別是驗證{}第一層的資料,包含canvasId,offsetx,offsety,width...等
  • 2.如果imgList存在,驗證其必填的資料是否填完
  • 3.同理,驗證wordsList中必須要填的資料
// 驗證offsetx,offsety,width,height,destWidth,destHeightd等必填引數
function checkCanvasToTempFileData(data){
  if ((!data.hasOwnProperty('offsetx')) || (typeof data.offsetx != 'number')){
    return false
  }
  if ((!data.hasOwnProperty('offsety')) || (typeof data.offsety != 'number')) {
    return false
  }
  if ((!data.hasOwnProperty('width')) || (typeof data.width != 'number')) {
    return false
  }
  if ((!data.hasOwnProperty('height')) || (typeof data.height != 'number'))  {
    return false
  }
  if ((!data.hasOwnProperty('destWidth')) || (typeof data.destWidth != 'number'))  {
    return false
  }
  if ((!data.hasOwnProperty('destHeight')) || (typeof data.destHeight != 'number'))  {
    return false
  }
  return true
}

// 驗證imgList中的source,x,y,width,height等必填引數
function checkImgsInfo(info){
  if ((!info.hasOwnProperty('source')) || (typeof info.source != 'string')){
    return false
  }
  if ((!info.hasOwnProperty('x')) || (typeof info.x != 'number')) {
    return false
  }
  if ((!info.hasOwnProperty('y')) || (typeof info.y != 'number')) {
    return false
  }
  if ((!info.hasOwnProperty('width')) || (typeof info.width != 'number')) {
    return false
  }
  if ((!info.hasOwnProperty('height')) || (typeof info.height != 'number')) {
    return false
  }

  return true
}

// 驗證word,x,y等必填引數
function checkWordsInfo(info){
  if ((!info.hasOwnProperty('x')) || (typeof info.x != 'number')) {
    return false
  }
  if ((!info.hasOwnProperty('y')) || (typeof info.y != 'number')) {
    return false
  }
  if ((!info.hasOwnProperty('word')) || (typeof info.word != 'string')) {
    return false
  }

  return true
}

1.在gk_draw中加入data的驗證程式碼,如下

    // 先顯示載入框
  wx.showLoading({
    title: '載入中...',
  })

    // 資料驗證
  if (!checkCanvasToTempFileData(data)){
    //報錯
    console.error('fail to draw , some params can not be empty')
    // 終止執行
    return 'fail'
  }
  
  // 獲取必須引數
  let mycanvasid = data.canvasId
  let offsetx = data.offsetx
  let offsety = data.offsety
  let width = data.width
  let height = data.height
  let destWidth = data.destWidth
  let destHeight = data.destHeight

2.驗證imgList和wordsList,因為canvas畫圖分享必然會繪製這兩者之一。

  // 此處如果需要指定圖片格式可自行加入相關程式碼
  // 及生成圖片質量等

  let wordsList = []
  let imgList = []

  if (data.hasOwnProperty('wordsList') && (typeof data.wordsList == 'object')){// 如果存在文字陣列
    
    wordsList = data.wordsList
  }

  if (data.hasOwnProperty('imgList') && typeof data.imgList == 'object') {// 如果存在圖片陣列
    
    imgList = data.imgList
  }

  let wlen = wordsList.length
  let ilen = imgList.length

  // 兩者不能同時為空,事實上,絕大多數繪圖都會同時繪製這兩樣元素
  if((wlen == 0) && (ilen == 0)){
    console.error('fail to draw , wordsList(array) and imgList(array) can not be empty both')
    return 'fail'
  }

3.繪製圖片

  // 建立ctx
  let ctx = wx.createCanvasContext(mycanvasid, this)

  // 這裡要先繪製圖片,因為先繪製文字的話,圖片會把文字覆蓋掉
  if (ilen > 0){
    for (let i = 0; i < ilen; i++) {
      let theImgInfo = imgList[i]
      
      //驗證資料
      if (!checkImgsInfo(theImgInfo)){
        console.error('fail to draw , some pramas of imgList can not be empty')
        return 'fail'
      }

      // 開始繪製圖片
      ctx.drawImage(theImgInfo.source, theImgInfo.x, theImgInfo.y, theImgInfo.width, theImgInfo.height)

    }
  }

4.繪製文字

    if (wlen > 0){
        
        // 初始化一些資料
        let color = 'black'
        if (data.hasOwnProperty('textColor') && (typeof data.textColor == 'string')) {
          color = data.textColor
        }
    
        let font = 35
        if (data.hasOwnProperty('font') && (typeof data.font == 'number')) {
          font = data.font
        }
        let textAlign = 'center'
        if (data.hasOwnProperty('textAlign') && (typeof data.textAlign == 'string')) {
          textAlign = data.textAlign
        }

        let textBaseLine = 'middle'
        if (data.hasOwnProperty('textAlign') && (typeof data.textBaseLine == 'string')) {
          textBaseLine = data.textBaseLine
        }
        
        
        for(let i=0;i<wlen;i++){
          let wordInfo = wordsList[i]

            // 驗證資料
            if (!checkWordsInfo(wordInfo)){
                console.error('fail to draw , some pramas of wordsList can not be empty')
                return 'fail'
             }
              
            // 設定具體渲染資訊
            let mycolor = color
            if (wordInfo.hasOwnProperty('textColor') && (typeof wordInfo.textColor == 'string')) {
                mycolor = wordInfo.textColor
             }
            let myfont = font
            if (wordInfo.hasOwnProperty('font') && (typeof wordInfo.font == 'number')) {
                myfont = wordInfo.font
            }
            let myTextAlign = textAlign
            if (wordInfo.hasOwnProperty('textAlign') && (typeof wordInfo.textAlign == 'string')) {
                myTextAlign = wordInfo.textAlign
            }
            let myTextBaseLine = textBaseLine
            if (wordInfo.hasOwnProperty('textAlign') && (typeof wordInfo.textBaseLine == 'string')) {
                myTextBaseLine = wordInfo.textBaseLine
            }
              
            ctx.setFillStyle(mycolor)
            ctx.setFontSize(myfont)
            ctx.setTextAlign(myTextAlign)
            ctx.setTextBaseline(myTextBaseLine)

            // 開始繪製文字
            ctx.fillText(wordInfo.word, wordInfo.x, wordInfo.y)
        }
    }

4.生成圖片

// 生成圖片
  ctx.draw(true,function(){
    wx.canvasToTempFilePath({
      canvasId: mycanvasid,
      x:offsetx,
      y:offsety,
      width:width,
      height:height,
      destWidth:destWidth,
      destHeight:destHeight,
      success:function(res){
          
        // 繪製成功後,返回該圖片的地址,並執行回撥函式
        dosomething(res.tempFilePath)
      },
      fail: function () {
        // 匯出圖片錯誤
        wx.showModal({
          title: '匯出圖片時出錯',
          content: '請重新嘗試!',
        })
      },
      complete: function () {
        wx.hideLoading()
      }
    }, this)
  })

5.暴露介面

module.exports = {
  gk_draw: gk_draw
}

6.使用

在要用的地方引入,如

// pages/result/result.js
const draw = require('../../utils/draw.js')

// 在需要的地方繪製

 /**
   * 生成圖片(介面版)
   */
  createImg:function(){

    let that = this
    let backgroundImg = '/images/result.jpg'
    let nickname = wx.getStorageSync('nickname')
    let imgurl = wx.getStorageSync('imgurl')

    let params = {
      canvasId:'share',
      textColor:'white',
      offsetx:0,
      offsety:0,
      width:750,
      height:1334,
      destWidth:750,
      destHeight:1334,
      imgList:[
        {
          source: backgroundImg,
          x:0,
          y:0,
          width:750,
          height:1334
        },
        {
          source: imgurl,
          x: 92,
          y: 80,
          width: 652,
          height: 690
        },
      ],
      wordsList:[
        {
          word:nickname,
          font:50,
          x: 375,
          y:1209
        }
      ]
    }

    draw.gk_draw(params,function(res){
      // 注,這裡的res就是圖片地址了
      that.setData({
        resultimg:res
      })
    })
  }
  

看看效果(截圖的,打下廣告)


9340937-a4d018d4f3f74cf7.png
22.png

完整版程式碼如下:

傳送門至draw.js

相關文章