4

小程序生成分享图片

DevTools上的动图效果


Canvas结构组成

从上面截图可以看出,canvas的主体组成部分包括:

  1. 背景图
  2. 微信头像和昵称信息
  3. 小程序码(小程序码会携带分享者的信息)

需要注意的问题

通过小程序提供的画布,我们能很容易的完成上面的整个海报的绘制。但是,canvas的绘制都是基于像素(px)级别的,但是在小程序中,我们的预览图和最终生成的图,都需要根据机型进行适配。

代码实现

  1. 底部弹出的模态框
    模态框的实现见下面的XML内容,可以看出具体组成如下:

    1. canvas外面套了一个modals,主要目的是让预览图看起来在DOM的最上面
    2. shareCanvas就是我们要绘制所有内容的画布
    3. modals-cancel-black是一层半透明的蒙层,尽量将页面的其它元素遮盖到它的下面
    4. 其次就是一个关闭按钮和相应的文字说明
<!-- 分享图 -->
  <view class="modals" hidden="{{isCanvasHidden}}">
    <canvas canvas-id="shareCanvas" class="share-canvas" style="width:680rpx;height:1000rpx" hidden="{{isCanvasHidden}}"></canvas>
    <view class="modals-cancel-black"></view>
    <view class="cancal-canvas-container" bindtap="hideCanvas">
      <image src="http://lc-adna4hf0.cn-n1.lcfile.com/13cb9cd1d79bbed4a47c.png" class="cancel-button"></image>
    </view>
    <view class="save-album-wrapper text-center">图片已保存到相册,可分享给好友</view>
  </view>
.modals {
  position: fixed;
  z-index: 999;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
}
.modals-cancel-black {
  position: absolute;
  z-index: 1000;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, .8);
}
  1. 绘制canvas

从动图可以发现,canvas的绘制是从点击底部模态框的“生成分享图”就开始了

drawShareCanvas: function () {
    // 1.计算相对比例rpx
    // 2.根据rpx绘制背景图片
    // 3.根据rpx绘制微信用户信息部分
    // 4.根据rpx绘制小程序码部分
}

上述的1-4的步骤之间,可能存在具体的先后关系,可以根据自己的情况来定制先后顺序

  1. rpx计算方法

wx.getSystemInfo的返回值中可以得到移动端屏幕的具体宽度,小程序WXSS是以rpx作为衡量单位,我们的canvas宽高都是以rpx计算的宽高。但是canvas绘制时以px为单位绘制。那么我们需要计算当前设备相对于375px的倍数。
后续的所有关于坐标、长宽或者距离都需要乘以这个rpx倍数,才能达到适配所有倍数。

var rpx = 1
wx.getSystemInfo({
  success (res) {        
    rpx = res.windowWidth / 375
    // ...
  }
})
  1. 绘制背景

因为背景图稍微比较大,在绘制时,通过ctx.scale()方法将画布整体都缩放了0.5倍

wx.showLoading({
  title: '绘制背景图'
})
wx.getImageInfo({
  src: 'http://lc-adna4hf0.cn-n1.lcfile.com/33111f0ba3409c8fdef9.png',
  success (res) {
    // 隐藏背景图loading
    wx.hideLoading()
    const ctx = wx.createCanvasContext('shareCanvas')

    // 绘制背景图
    ctx.scale(0.5, 0.5)
    ctx.drawImage(res.path, 0, 0, 680 * rpx, 1000 * rpx)
    ctx.stroke()

    // 绘制其它
  },
  fail () {
    wx.showToast({
      title: '背景图获取失败',
      icon: 'none'
    })
  }
})
  1. 微信用户信息

微信个人图像需要绘制成圆形的,可以用CanvasContext.arc(...)来绘制一个圆弧实现。圆弧会有默认的黑色边框。清除边框使用ctx.setLineWidth(0)。如果需要自定义边框颜色,可以使用ctx.setStrokeStyle('red')来设定。
   未指定ctx.clip()时,图片不会被裁剪,看到的图片还是正方形。通过不指定裁剪的方式(注释ctx.clip),可以来精确确定图像的开始点的位置。图像的开始绘制点位置为:圆心横坐标 - 圆弧的半径 = point - radius

let userinfo = wx.getStorageSync('userinfo')
if (userinfo) {
  wx.showLoading({
    title: '绘制用户信息',
  })
  // 绘制个人信息部分
  wx.getImageInfo({
    src: userinfo.avatarUrl,
    success (avatar) {
      // 隐藏用户信息loading
      wx.hideLoading()
      let radius = 50 * rpx
      let point = 70 * rpx

      ctx.scale(1, 1)
      ctx.save()
      ctx.beginPath()
      ctx.setLineWidth(0)
      ctx.arc(point, point, radius, 0, 2 * Math.PI)
      ctx.setLineWidth(0)
      ctx.closePath()                  
      ctx.save()
      ctx.clip()
      ctx.drawImage(avatar.path, (point - radius), (point - radius), 100 * rpx, 100 * rpx)
      ctx.restore()
    },
    fail (err) {
      wx.showToast({
        title: '获取用户头像失败',
        icon: 'none'
      })
    }
  })

  // 昵称
  ctx.setFontSize(32 * rpx)
  ctx.setFillStyle('white')
  ctx.fillText(userinfo.nickName, 140 * rpx, 60 * rpx)
  ctx.stroke()
                            
  // 称谓
  ctx.setFontSize(24 * rpx)
  ctx.setFillStyle('#FEFEFE')
  ctx.fillText('尚德学习大使', 140 * rpx, 100 * rpx)
  ctx.stroke()
}
  1. 绘制小程序码
    小程序码因为会携带分享着信息,需要通过调用后台接口来动态生成,所以是异步的。可以在生成期间loading给用户返回进度。得到小程序码的url地址后,就可以根据前面的绘制方法,找到小程序码的位置绘制即可。
  2. 保存到系统相册

所有绘制步骤完成后,此时还剩两个事情是待做的。一、保存canvas画布内容到系统相册;二、显示预览图。
根据整张图的制作顺序,小程序码是最后一步完成的,所以小程序码绘制到canvas后,算是所有绘制动作完成,需要执行

ctx.draw()
// 下一步?=> 保存图片到相册
saveAlbum()
// 显示预览图
that.setData({
    isCanvasHidden: false
})

按照上面的方式,我们执行saveAlbum后发现,在开发工具上,每次都能正常的弹出保存图片。但是在真机上测试时,偶尔会发现预览图已经出来了,但是到系统相册查看,却发现没有生成想要的图片。
通过查看小程序开发文档才知道,wx.canvasToTempFilePath方法需要在ctx.draw的回调中执行(文档地址)。所以,实际得这么干,毕竟人家定义接口的是大爷(嘻嘻)。

ctx.stroke()

ctx.draw(false, () => {
  that.saveAlbum()
})
// 这个段也可以放第四行之后
that.setData({
  isCanvasHidden: false
})

保存到相册的相关代码

saveAlbum() {
    var that = this
    wx.canvasToTempFilePath({
      x: 0,
      y: 0,
      canvasId: 'shareCanvas',
      success: function (res) {
        wx.saveImageToPhotosAlbum({
          filePath: res.tempFilePath,
          success(saveRes) {
            wx.showToast({
              title: '保存成功',
              icon: 'success'
            })
          },
          fail() {
            wx.showToast({
              title: '保存失败',
              icon: 'none'
            })
            that.hideCanvas()
          }
        })
      }
    })
  }

手机端效果展示

不支持mp4预览,转换成gif太大了。
WeChat_20181031180605.mp4

参考文档

小程序Canvas API: https://developers.weixin.qq.com/miniprogram/dev/api/canvas/CanvasContext.html

本篇文章由一文多发平台ArtiPub自动发布


dino小恐龙
107 声望1 粉丝

2020,愿世界没有灾难,一切皆如春暖花开