小程序Canvas生成分享海报避坑指南

关于小程序使用Canvas画布生成分享海报,网上有很多的教程,但是结合到自己的业务上面就BUG百出了 接下来分享一下我的经历

微信出了新的api

微信公众平台目前更新不是太即使,查问题好多帖子都是一两年都没有回应(像授权获取用户的信息,说是马上要废弃,但新的api又不是太方便,用老的又不安心)...  关于画布我们就按照文档上最新的来写

WXML

直接用内置标签 平面海报 type 就是 2d  样式给上尺寸 把它定位移出 可视范围  虽然是用JS在画布范围内 作画的 但最终是调用api 生成图片 在行内有就可以 不用显示

<canvas  style="z-index: 99;position: fixed; left: -9990px;top: 0px;width: 750rpx;height: 1200rpx;background: #fff;" id="myCanvas" type="2d" />

JS

初始化画布 找到 WXML中的元素 设置大小 获得分辨率 可以适配不同的机型

    // 具体画布实现
    createNewImg(){
      var that = this;
      wx.createSelectorQuery()
      .select('#myCanvas') // 在 WXML 中填入的 id
      .fields({ node: true, size: true })
      .exec((res) => {
        // Canvas 对象
        const canvas = res[0].node
        // Canvas 画布的实际绘制宽高
        const renderWidth = res[0].width
        const renderHeight = res[0].height
        // Canvas 绘制上下文
        const context = canvas.getContext('2d')
        // 初始化画布大小 适配不同屏幕的分辨率
         const dpr = wx.getWindowInfo().pixelRatio
         canvas.width = renderWidth * dpr
         canvas.height = renderHeight * dpr
         context.scale(dpr, dpr)
         const ratio = wx.getSystemInfoSync()?.windowWidth / 750; // 各机型的比例
        // 绘制前清空画布
        context.clearRect(0, 0, canvas.width, canvas.height)
        // 设置背景为白色
        context.fillStyle = "#fff";
        context.fillRect(0, 0, canvas.width, canvas.height)
       
      // 作画顺序当中 加载图片是异步的 想让加载顺序按照自己书写的顺序来 就用Promise 写成链式调用
      that.canvasAvatar(canvas,context,ratio).then(res => {
        return that.canvasCover(canvas,context,ratio)
      }).then(cov => {
        return that.canvasCode(canvas,context,ratio)
      }).then(cod =>{
        that.canvasImg(canvas)
      })
    })
    },

 作画时确保需要的数据已经就绪

常见的分享推广海报元素中有  微信 昵称 头像  封面图  小程序码 当在画布中插入动态文字或者图片时保证 数据已经请求到 或者 || 表示备用数据 不然就会导致 海报生成失败 并且不报错 也不好排查错误

在我的开发环境中 用户授权登录后 头像和昵称储存在 wx.StorageSync中 封面图和小程序码是又发起的网络请求 所以画布的开始作画时间选择 在了初始化数据请求(自行封装网络请求)成功之后

    // 获取数据
    initData(wsg){
      let data = {
        method : 'POST',
        url:'orderCase/getCaseInfo',
        data:{id:wsg}
    }
    let that = this
      httpUtils.request(data).then(res=>{
        if(res.data.data){ // 确定成功获取到了数据
          that.createNewImg() // 开启作画
          // 设置标题
          wx.setNavigationBarTitle({
            title: res.data.data.caseName
          })
          that.setData({
            footerNavList:res.data.data, // 赋值列表数据
          })
...

 作画时异步插入图片的顺序和堵塞

以上都梳理好之后 生成海报可以成功 但是会出现 缺漏项 少了标题? 少了小程序码? 少了封面图?等等 

解决方案 : 作画顺序当中 加载图片是异步的 想让加载顺序按照自己书写的顺序来 就用Promise 写成链式调用  并且每个阶段要连接步骤 

      context.save()   context.beginPath()  使用这两个api

设置头像 如果  没有 ave  就用默认的logo  -- 使用裁剪

    // 设置 头像 设置头像 (裁剪圆形)
    canvasAvatar(canvas,context,ratio) {
      return new Promise(res => {
      const image = canvas.createImage()
      image.src = wx.getStorageSync('ave') || wx.getStorageSync('storeData').logo
      image.onload = () => {
      context.save()
      context.beginPath()
      context.arc(125 *ratio, 90*ratio, 50*ratio, 0, Math.PI * 2)
//    裁剪一个圆形  圆心的位置    圆的半径   0  2派 就是弧度完整的 圆
      context.clip(); // 裁剪
      context.drawImage(image,75*ratio,40*ratio,100*ratio,100*ratio)
//   向画布内写入图片  图片对象  x轴的位置  y 轴的位置  宽  高
      context.restore()
        console.log('头像完成')
        res(true)
    }
      })
    },

设置封面图和文字 -- 封面图有三种尺寸  尺寸不同 文字布局也要发生改变

    // 设置封面图
    canvasCover(canvas,context,ratio){
      let that = this;
      return new Promise(cov => {
     // 增加用户名 提示语 案例名 
    context.font = '20px WenQuanYi Micro Hei'
    context.fillStyle = 'black';
    context.fillText(that.data.userInfo.nickName, 204*ratio, 108*ratio);

    context.font = '12px WenQuanYi Micro Hei'
    context.fillStyle = '#444';
    context.fillText('为您分享', 566*ratio, 108*ratio);
    // 封面尺寸是长图的 文字布局
if(wx.getStorageSync('nowCaseCover') === 1 || wx.getStorageSync('nowCaseCover') === 3){
  context.font = '18px WenQuanYi Micro Hei'
  context.fillStyle = 'black';
  context.fillText(that.data.footerNavList.caseName, 400*ratio, 1060*ratio);

  context.font = '13px WenQuanYi Micro Hei'
  context.fillStyle = 'black';
  context.fillText('长按识别,阅览全文', 400*ratio, 1104*ratio);
      // 封面尺寸是横图 或者方图的 文字布局
}else{
        context.font = '18px WenQuanYi Micro Hei'
      context.fillStyle = 'black';
      context.textAlign = 'center';
      context.fillText(that.data.footerNavList.caseName, 375*ratio, 860*ratio);

      context.font = '13px WenQuanYi Micro Hei'
      context.fillStyle = 'black';
      context.textAlign = 'center';
      context.fillText('长按识别,阅览全文', 375*ratio, 1128*ratio);
}

        const image3 = canvas.createImage()
        image3.src =  that.data.footerNavList.microCoverUrl
        image3.onload = () => {
        setTimeout(() => {
          context.save()
          context.beginPath()
          // nowCaseCover 1 3 长图   2 横图  4 方图
          if(wx.getStorageSync('nowCaseCover') === 4){
            context.drawImage(image3,75*ratio,180*ratio,600*ratio,600*ratio)
          }else if(wx.getStorageSync('nowCaseCover') === 1 || wx.getStorageSync('nowCaseCover') === 3){
            context.drawImage(image3,135*ratio,180*ratio,480*ratio,750*ratio)
          }else if(wx.getStorageSync('nowCaseCover') === 2){
            context.drawImage(image3,-45*ratio,220*ratio,860*ratio,540*ratio)
          }
          context.restore()
          console.log('封面图完成')
        cov(true)
      }, 250);
    }
      })
    },

最后请求小程序码 异步请求

    // 设置小程序码
    canvasCode(canvas,context,ratio){
      let that = this
      return new Promise(cod => {
        const image5 = canvas.createImage()
        let data = {
          method : 'POST',
          url:'weChat/getQrCode',
          data:{
            storeId:wx.getStorageSync('storeData').id,
            appid:wx.getStorageSync('appId'),
            path:'pages/homeDetail/index',
            clerkId:wx.getStorageSync('storeData').clerkId,
            caseId:that.data.id
          }
      }
      httpUtils.request(data).then(res=>{
          image5.src =  res.data.data
    })
        image5.onload = () => {
        setTimeout(() => {
          context.save()
          context.beginPath()
          // 小程序码的布局 随封面图尺寸 变化
          if(wx.getStorageSync('nowCaseCover') === 1 || wx.getStorageSync('nowCaseCover') === 3){
            context.drawImage(image5,75*ratio,980*ratio,150*ratio,150*ratio)
          }else{
            context.drawImage(image5,300*ratio,900*ratio,150*ratio,150*ratio)
          }
          context.restore()
          console.log('小程序码完成')
        cod(true)
      }, 250);
    }
      })
    },

绘制完成 生成图片的 临时路径并 展示

    //canvas生成图片  
    canvasImg(canvas) {
      let that = this;
      wx.canvasToTempFilePath({
              canvas,
              success: function (res) {
                that.setData({
                  imagePath: res.tempFilePath, // 临时路径储存起来
                  prowerS:true // 表示分享海报 已经生成完成
                });
              },
              fail: function (err) {
                console.log(err);
              }
            });
    },

一些其他优化

1.开始作画时间  刚一进入页面就开始 作画(数据请求过来之后) 等到用户主动分型生成的时候 也画的差不多了

2.主动生成的时候 依据 prowerS:true 判断(轮询)完成情况 节省内存 当前页面只需 生成一次

主动生成的回调:

    // 显示分享海报
    drawPoster(e){
      // 保险 先关闭之前的
      this.setData({
        maskHidden: false,
      });
      wx.showLoading({
        title: '生成海报中',
      })
      let that = this
      // 300ms 轮询一次 生成完成后 清除定时器
      let wsg = setInterval(() =>{
        if(this.data.prowerS){
        wx.hideLoading()
        that.setData({
          maskHidden: true,
        })       
        that.dynamic({
        behaviorType: '2',
        behaviorDescription: '分享了微官网的案例'
      })
      clearInterval(wsg)
      }
      },300)
    },

页面展示  show-menu-by-longpress="true"  开启图片长按菜单

<view class='imagePathBox' hidden="{
   
   {maskHidden == false}}" catchtap="close" catchtouchmove='stopScroll'>
      <image src="{
   
   {imagePath}}" class='shengcheng' show-menu-by-longpress="true" mode="aspectFill"></image>
    </view>

猜你喜欢

转载自blog.csdn.net/benlalagang/article/details/129486983