在微信小程序使用canvas生成海报,并保存图片在本地


目标:在微信小程序中页面有一个按钮,点击后生成一张海报,点击保存,图片保存到本地相册

海报样式如下:

 通过观摩别人代码,分析这张海报,难点有四个,一是背景的圆角,canvas并没有一个api是画圆角的,二是中间的两行标题,这里应该是动态的,可能一行可能两行,三是圆形头像处理,四是,画出的海报如何在点击下载的时候很好的布局。最终成果如下:

 wxml:

<view class="btn" bindtap="share">点我生成海报view>
<canvas canvas-id="firstCanvas" class="canvas-exp">canvas>
<view hidden='{{previewHidden}}' class='preview'>
  <image src='{{preurl}}' mode='widthFix' class='previewImg' style="height:{{currentLineHeight}}px">image>
  <button bindtap='eventSave' class="btnSave">保存到相册button>
view>

wxss:

.btn {
  text-align: center;
  margin-top: 300rpx;
}


.btnSave {
  width: 408rpx;
  height: 92rpx;
  line-height: 92rpx;
  background: #FFFFFF;
  border-radius: 46rpx;
  text-align: center;
  font-size: 36rpx;
  font-weight: 600;
  color: #FF6C5D;
  margin-top:30rpx;
}

.preview{
  width: 100%;
  height: 100%;
  background: rgba(0,0,0,0.7);
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 10;
}

.canvas-exp {
  position: fixed;
  bottom: 0;
  right: 100%;
  width: 100%;
  height: 100%;
  background: transparent;
}

.previewImg {
  width: 560rpx;
  border-radius:56rpx;
  margin-top: 64rpx;
  margin-left:12.8%;
}

js中的data:

data: {
    windowW: 0,
    windowH: 0,
    show: false,
    bgpic: '',
    propic: '',
    qCord: '',
    picImg: '',
    previewHidden: true,
    preurl: '',
    currentLineHeight:0
  },

js中的onLoad:

onLoad: function (options) {
    let that = this
    // 获取设备宽高
    wx.getSystemInfo({
      success: function (res) {
        that.setData({
          windowW: res.windowWidth,
          windowH: res.windowHeight
        })
      }
    })
    that.setData({
      bgpic: '../../../assets/head.png',
      propic: '../../../assets/postbg.png',
      qCord: '../../../assets/code.png',
      picImg: '../../../assets/backImg.png'

    })
    that.drawCanvas()
  }

在onLoad,海报中的图片资源应该是动态的可以在这请求好,在这边已经生成海报,考虑海报在点击以后再去生成要等待时间过长,图片资源下载失败还可以在页面加载之后再去请求。

canvas绘制海报函数:

  drawCanvas() {
    let that = this
    let windowW = that.data.windowW
    let windowH = that.data.windowH
    let ctx = wx.createCanvasContext('firstCanvas')
    let text = '健康大使就发生了的打扫房间了放大设计方案放大镜双方就安分'
    let row = []
    let strLen = text.length
    let rowNum = 0
    // 计算文字行数
    row.push(text.slice(0, 12))
    rowNum = 1
    if (strLen > 12 && (strLen <= 24)) {
      rowNum = 0
      row.push(text.slice(12, 24))
    }
    if (strLen > 24) {
      rowNum = 0
      row.push(text.slice(12, 22) + '...')
    }

    ctx.drawImage(that.data.propic, (windowW - 279) / 2, 32, 279, (460 - rowNum * 26))
    that.setData({
      currentLineHeight: 460 - rowNum * 26
    })
    ctx.setFillStyle('#FFFFFF')
    // 白色圆角背景
    let x = (windowW - 256) / 2
    let y = 62
    let r = 24
    let w = 256
    let h = 344 - rowNum * 26
    ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5)
    ctx.moveTo(x + r, y)
    ctx.lineTo(x + w - r, y)
    ctx.lineTo(x + w, y + r)
    ctx.arc(x + w - r, y + r, r, Math.PI * 1.5, Math.PI * 2)
    ctx.lineTo(x + w, y + h - r)
    ctx.lineTo(x + w - r, y + h)
    ctx.arc(x + w - r, y + h - r, r, 0, Math.PI * 0.5)
    ctx.lineTo(x + r, y + h)
    ctx.lineTo(x, y + h - r)
    ctx.arc(x + r, y + h - r, r, Math.PI * 0.5, Math.PI)
    ctx.lineTo(x, y + r)
    ctx.lineTo(x + r, y)
    ctx.fill()

    // 识别小程序二维码
    ctx.drawImage(that.data.qCord, (windowW - 236) / 2 + 173, 32 + 387, 60, 60)
    ctx.setFillStyle("#ffffff")
    ctx.setFontSize(13)
    ctx.fillText('长按识别二维码', (windowW - 236) / 2, 32 + 412 - rowNum * 26)
    ctx.setFillStyle("#ffffff")
    ctx.setFontSize(13)
    ctx.fillText('查看TA发布的全部精华内容', (windowW - 236) / 2, 32 + 433 - rowNum * 26)
    ctx.setFillStyle("#0B0D0E")
    ctx.setFontSize(15)
    ctx.fillText('健康医生', (windowW - 218) / 2 + 48, 32 + 345 - rowNum * 26)

    let collectImg = '../../../assets/hexagon.png'
    ctx.drawImage(collectImg, (windowW - 218) / 2, 92, 18, 20)
    ctx.font = 'normal bold 14px PingFang-SC-Medium';
    ctx.setFillStyle("#AF8B43")
    ctx.fillText('精选内容', (windowW - 218) / 2 + 24, 108)
    ctx.drawImage(that.data.picImg, (windowW - 218) / 2, 32 + 160 - rowNum * 26, 213, 136)

    // 圆形头像
    let avatarurl_width = 38
    let avatarurl_heigth = 38
    let avatarurl_x = (windowW - 218) / 2
    let avatarurl_y = 352 - rowNum * 26
    ctx.save()
    ctx.beginPath()
    ctx.arc(avatarurl_width / 2 + avatarurl_x, avatarurl_heigth / 2 + avatarurl_y, avatarurl_width / 2, 0, Math.PI * 2, false)
    ctx.clip()
    ctx.drawImage(that.data.bgpic, avatarurl_x, avatarurl_y, avatarurl_width, avatarurl_heigth)
    ctx.restore()
    for (let b = 0; b < row.length; b++) {
      ctx.font = 'normal bold 22px PingFang-SC-Medium';
      ctx.setFillStyle("#0B0D0E")
      ctx.fillText(row[b], (windowW - 218) / 2, 144 + b * 26, 218)
    }
    ctx.draw()
  },

由于标题数据动态,不知道是一行还是两行,如果超过两行只显示两行,且以省略号结尾,这会导致整张海报的高度发生改变,整个布局都会受影响,因而先计算标题行数。若先绘制标题,会被后面的白色背景覆盖,后文的文字也会带上标题的加粗等样式,所以放在了最后面。

点击生成海报,把海报转成图片显示在页面:

  share: function () {
    var that = this
    wx.showLoading({
      title: '努力生成中...'
    })
    wx.canvasToTempFilePath({
      x: (that.data.windowW - 279) / 2,
      y: 32,
      width: 279,
      height: that.data.currentLineHeight,
      canvasId: 'firstCanvas',
      fileType: 'jpg',
      quality: 1,
      success: function (res) {
        console.log(res.tempFilePath);
        that.setData({
          preurl: res.tempFilePath,
          previewHidden: false,
        })
        wx.hideLoading()
      },
      fail: function (res) {
        console.log(res)
      }
    })
  }

本来设计页面是点击生成海报,canvas跟保存海报按钮放在灰色背景上,但是在不同的设备下,保存海报按钮跟海报距离相差太大,因而改成页面加载,在可视范围之外把canvas画好,点击生成海报时,只是把海报转成图片,变成图片和按钮的布局

点击保存海报:

  eventSave() {
    let that =this
    wx.saveImageToPhotosAlbum({
      filePath: this.data.preurl,
      success(res) {
        wx.showToast({
          title: '保存图片成功',
          icon: 'success',
          duration: 2000
        })
        that.setData({
          previewHidden: true,
        })
      }
    })
  }