小程序通过canvas2d生成海报图分享

589 阅读8分钟

1.jpg2.jpg

需要完成的需求:

一张图片上面有二维码、头像、文字 头像、文字都是从接口拿、二维码是根据不同的业务场景达到分享的目的

需要解决的问题:

如何通过wx.saveImageToPhotosAlbum把需要的东西都能生成出来

解决的思路:

通过小程序canvas 画图把对应的二维码和头像和文字整合到一个图片上再通过wx.saveImageToPhotosAlbum保存下来

解决方案:

1:首先页面有一个<canvas></canvas>标签去放入需要wx.saveImageToPhotosAlbum保存的图片 2:生成的二维码: 可能生成小程序码、也可能生成二维码(跳转h5)、二者都有就需要二个<canvas></canvas>标签

实现方式:

小程序的<canvas></canvas>换成用type=2d模式 以前的模式不在维护了 <canvas> type="2d" id="canvas" style="这里写自己需要的海报大小"></canvas> 这个canvas标签相当于对应的海报的格式, 然后就是获取这个标签

query.select('#myQrcode')
  .fields({
    node: true,
    size: true
  })
  .exec((res) => {
    const canvas = res[0].node
    const ctx = canvas.getContext('2d')
    const dpr = wx.getSystemInfoSync().pixelRatio // 这边的dpr主要给生成canvas时候使用
    ctx.scale(dpr, dpr)
    // 可以在这里定义一个posterDatas 把这个canvas、ctx、dpr 存进去
    // 获取背景图 可以定义个promise
    const promise1 = new Promise(function (resolve, reject) {
      const posterBg = canvas.createImage();
      posterBg.src = "自己需要的图片";
      posterBg.onload = (e) => {
        resolve(posterBg);
      }
    });
    Promise.all(promise1).then(res => {
      // 这边都可以去绘制对应的图片了
      ctx.drawImage(res[0], 0, 0, "初始化图片的宽度", "初始化图片的高度"); //  (图片,左距离,上距离,宽(px as Number),高(px as Number))
      // 这时候都可以通过wx.canvasToTempFilePath 保存canvas 了然后通过success里面的tempFilePath去在 wx.saveImageToPhotosAlbum里面保存图片了
      一个背景图的海报都生成了 下面开始第二个
    })
  })

现在我们需要把二维码也放上去 这时候生成 小程序码是属于简单的情况 这个一般由公司后端去调小程序现有的接口 然后给你个data值

  const query = wx.createSelectorQuery()
  query.select('自己定义的小程序的canvas')
    .fields({
      node: true,
      size: true
    })
    .exec((res) => {
      const miniCanvas = res[0].node
      const miniCtx = miniCanvas.getContext('2d')
      const dpr = wx.getSystemInfoSync().pixelRatio
      // 这一套其实都和前面获取海报图一样 
      miniCtx.scale(dpr, dpr);
      getMiniQrUrl("接口需要的参数").then((res) => {
        let promise1 = new Promise((resolve, reject) => {
          const photo = miniCanvas.createImage();
          photo.src = "接口需要的二维码";
          photo.onload = (e) => {
            resolve(photo);
          }
        });
        Promise.all([promise1]).then(res => {
          // 这里可以在这里定义 也可以下面用的时候写死 看个人习惯
          let miniUrl_width = 70; // 二维码的宽度
          let miniUrl_heigth = 70; // 二维码的高度
          let miniUrl_x = 0; // 二维码距离canvas的x轴距离
          let miniUrl_y = 0; // 二维码距离canvas的y轴距离
          // 下面这几部是把小程序二维码变为圆形 不需要可以跳过
          // start
          miniCtx.save(); // 开始绘制 不写好像也可以
          // 这个可以看小程序文档写的 比较清晰 主要是为了画圆
          miniCtx.arc(miniUrl_width / 2 + miniUrl_x, miniUrl_heigth / 2 + miniUrl_y, miniUrl_width / 2, 0, Math.PI * 2, false);
          // 裁切
          miniCtx.clip()
          // end
          miniCtx.drawImage(res[0], miniUrl_x, miniUrl_y, miniUrl_width, miniUrl_heigth);
          wx.canvasToTempFilePath({
            // 把当前画布指定区域的内容导出生成指定大小的图片
            canvas: "自己定义的ID", // canvas实例
            success: res => {
              this.setData({
                miniCodeUrl: res.tempFilePath,
                //我们当这个url为miniCodeUrl  这边拿到图片路径存在自己的data里面 因为还需要把这个图片放在海报图中
              })
            }
          })
        })
      })
    })
}
const query = wx.createSelectorQuery()
query.select('#myQrcode')
  .fields({
    node: true,
    size: true
  })
  .exec((res) => {
    const canvas = res[0].node
    const ctx = canvas.getContext('2d')
    const dpr = wx.getSystemInfoSync().pixelRatio // 这边的dpr主要给生成canvas时候使用
    ctx.scale(dpr, dpr)
    // 可以在这里定义一个posterDatas 把这个canvas、ctx、dpr 存进去
    // 获取背景图 可以定义个promise
    const promise1 = new Promise(function (resolve, reject) {
      const posterBg = canvas.createImage();
      posterBg.src = "自己需要的图片";
      posterBg.onload = (e) => {
        resolve(posterBg);
      }
    });
    // 这时候我们有小程序码了
    const promise2 = new Promise(function (resolve, reject) {
      const miniCode = canvas.createImage();
      miniCode.src = "小程序的url::miniCodeUrl";
      miniCode.onload = (e) => {
        resolve(miniCode);
      }
    });
    Promise.all(promise1, promise2).then(res => {
      // 这边都可以去绘制对应的图片了
      ctx.drawImage(res[0], 0, 0, "初始化图片的宽度", "初始化图片的高度"); //  (图片,左距离,上距离,宽(px as Number),高(px as Number))
      ctx.drawImage(res[1], "左距离", "上距离", "宽", "高");
      // 这时候都可以通过wx.canvasToTempFilePath 保存canvas 了然后通过success里面的tempFilePath去在 wx.saveImageToPhotosAlbum里面保存图片了
    })
  })

然后小程序二维码就画到海报图里面去了 这时候可能有的业务是别的二维码 这时候就需要使用第三方插件了

这时候github 上有一个github.com/yingye/weap… 生成小程序二维码按照文档生成就行了

但是我这边遇到个问题 用这种方式生成的二维码 在安卓手机上会有个别几个生成的二维码扫不出来 不知道什么问题 有踩坑的大佬还得请指教一下

这个时候我想到了能不能生成base64的格式再去使用 于是在网上找到了github.com/Pudon/weapp… 这个插件,但是这时候我又遇到坑了,生成得base64 再去画图时候拿不到这个链接、这时候就想去把base64再转成网络图片 然后有需求想在二维码中加一个图标的 可以和上面加二维码一样 再起一个 promise 然后放在二维码中间 下面是生成base64的二维码的方案

  let linkUrl = "一般是h5页面的路径"
  let base64 = QR.drawImg(linkUrl, {
    typeNumber: 4,
    errorCorrectLevel: 'M',
    size: 500
  })
  // 这边拿到base64 去直接生成还是有点问题  所以我想到了把base64转为图片 再去生成
  this.base64src(base64, res => {
    console.log(res) // 返回图片地址,直接赋值到image标签即可
    this.setData({
      qrCodeImage: res
    })
  });
}
// 这边是网上找的base64转为图片的方法
base64src(base64data, cb) {
  const fsm = wx.getFileSystemManager();
  const FILE_BASE_NAME = 'code'; //自定义文件名
  const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64data) || [];
  if (!format) {
    return (new Error('ERROR_BASE64SRC_PARSE'));
  }
  const filePath = `${wx.env.USER_DATA_PATH}/${FILE_BASE_NAME}.png`;
  const buffer = wx.base64ToArrayBuffer(bodyData);
  fsm.writeFile({
    filePath,
    data: buffer,
    encoding: 'binary',
    success() {
      cb(filePath);
    },
    fail() {
      return (new Error('ERROR_BASE64SRC_WRITE'));
    },
  });
}
const query = wx.createSelectorQuery()
query.select('#myQrcode')
  .fields({
    node: true,
    size: true
  })
  .exec((res) => {
    const canvas = res[0].node
    const ctx = canvas.getContext('2d')
    const dpr = wx.getSystemInfoSync().pixelRatio // 这边的dpr主要给生成canvas时候使用
    ctx.scale(dpr, dpr)
    // 可以在这里定义一个posterDatas 把这个canvas、ctx、dpr 存进去
    // 获取背景图 可以定义个promise
    const promise1 = new Promise(function (resolve, reject) {
      const posterBg = canvas.createImage();
      posterBg.src = "自己需要的图片";
      posterBg.onload = (e) => {
        resolve(posterBg);
      }
    });
    // 这时候我们有小程序码了
    const promise2 = new Promise(function (resolve, reject) {
      const miniCode = canvas.createImage();
      miniCode.src = "小程序的url::miniCodeUrl";
      miniCode.onload = (e) => {
        resolve(miniCode);
      }
    });
    const promise3 = new Promise(function (resolve, reject) {
      const qrCodeImage = canvas.createImage();
      qrCodeImage.src = "h5二维码的url::qrCodeImage";
      qrCodeImage.onload = (e) => {
        resolve(qrCodeImage);
      }
    });
    Promise.all(promise1, promise2, promise3).then(res => {
      // 这边都可以去绘制对应的图片了
      ctx.drawImage(res[0], 0, 0, "初始化图片的宽度", "初始化图片的高度"); //  (图片,左距离,上距离,宽(px as Number),高(px as Number))
      ctx.drawImage(res[1], "左距离", "上距离", "宽", "高");
      ctx.drawImage(res[3], "左距离", "上距离", "宽", "高");
      // 这时候都可以通过wx.canvasToTempFilePath 保存canvas 了然后通过success里面的tempFilePath去在 wx.saveImageToPhotosAlbum里面保存图片了
    })
  })

这时候还有头像和问题 头像我都不说了 其实就是和生成二维码一样的模式 文字的话官网的api也很详细 文字的话主要是处理一个换行的问题下面代码片端 会有个换行的方法 也是网上找的一个方法 然后自己二次开发了一下blog.csdn.net/qq_39905409…

drawPosterCode() {
  let linkUrl = "一般是h5页面的路径"
  let base64 = QR.drawImg(linkUrl, {
    typeNumber: 4,
    errorCorrectLevel: 'M',
    size: 500
  })
  // 这边拿到base64 去直接生成还是有点问题  所以我想到了把base64转为图片 再去生成
  this.base64src(base64, res => {
    console.log(res) // 返回图片地址,直接赋值到image标签即可
    this.setData({
      qrCodeImage: res
    })
  });
}
// 这边是网上找的base64转为图片的方法
base64src(base64data, cb) {
  const fsm = wx.getFileSystemManager();
  const FILE_BASE_NAME = 'code'; //自定义文件名
  const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64data) || [];
  if (!format) {
    return (new Error('ERROR_BASE64SRC_PARSE'));
  }
  const filePath = `${wx.env.USER_DATA_PATH}/${FILE_BASE_NAME}.png`;
  const buffer = wx.base64ToArrayBuffer(bodyData);
  fsm.writeFile({
    filePath,
    data: buffer,
    encoding: 'binary',
    success() {
      cb(filePath);
    },
    fail() {
      return (new Error('ERROR_BASE64SRC_WRITE'));
    },
  });
}
const query = wx.createSelectorQuery()
query.select('#myQrcode')
  .fields({
    node: true,
    size: true
  })
  .exec((res) => {
    const canvas = res[0].node
    const ctx = canvas.getContext('2d')
    const dpr = wx.getSystemInfoSync().pixelRatio // 这边的dpr主要给生成canvas时候使用
    ctx.scale(dpr, dpr)
    // 可以在这里定义一个posterDatas 把这个canvas、ctx、dpr 存进去
    // 获取背景图 可以定义个promise
    const promise1 = new Promise(function (resolve, reject) {
      const posterBg = canvas.createImage();
      posterBg.src = "自己需要的图片";
      posterBg.onload = (e) => {
        resolve(posterBg);
      }
    });
    // 这时候我们有小程序码了
    const promise2 = new Promise(function (resolve, reject) {
      const miniCode = canvas.createImage();
      miniCode.src = "小程序的url::miniCodeUrl";
      miniCode.onload = (e) => {
        resolve(miniCode);
      }
    });
    const promise3 = new Promise(function (resolve, reject) {
      const qrCodeImage = canvas.createImage();
      qrCodeImage.src = "h5二维码的url::qrCodeImage";
      qrCodeImage.onload = (e) => {
        resolve(qrCodeImage);
      }
    });
    Promise.all(promise1, promise2, promise3).then(res => {
      // 这边都可以去绘制对应的图片了
      ctx.drawImage(res[0], 0, 0, "初始化图片的宽度", "初始化图片的高度"); //  (图片,左距离,上距离,宽(px as Number),高(px as Number))
      ctx.drawImage(res[1], "左距离", "上距离", "宽", "高");
      ctx.drawImage(res[3], "左距离", "上距离", "宽", "高");
      // 这时候都可以通过wx.canvasToTempFilePath 保存canvas 了然后通过success里面的tempFilePath去在 wx.saveImageToPhotosAlbum里面保存图片了
      // 处理文字
      ctx.font = "10px Arial"; // 文字的大小和字体
      ctx.fillStyle = "#fff"; // 文字的颜色
      ctx.textAlign = "left" // *** 文字的居中方式 *** 当时没设置这个 被坑了蛮久
      toFormateStr(ctx, str, draw_width, lineNum, startX, startY, steps) // 然后直接调用这个方法 达到换行的需求
    })
  })

/* 
@function 处理canvas换行
@param ctx {CanvasRenderingContext2D}  canvas绘图上下文
@param str {String} 需要传入的文字
@param draw_width {Number} 绘制后的文字显示宽度
@param lineNum {Number}  最大行数,多出部分用'...'表示, 如果传-1可以达到自动换行效果
@param startX {Number}  绘制文字的起点 X 轴坐标
@param startY {Number}  绘制文字的起点 Y 轴坐标
@param steps {Number}  文字行间距
*/
toFormateStr(ctx, str, draw_width, lineNum, startX, startY, steps) {
  if (str) {
     // 这边是判断文字是否有换行 如果有换行都按照换行 业务处理方式
    if (str.indexOf("\n") != -1) {
      let s = str.split("\n");
      let ss = s.reverse()
      for (let i = 0; i < ss.length; i++) {
        ctx.fillText(s[i], startX, startY - steps * i, 120);
      }
    } else {
      let strWidth = ctx.measureText(str).width; // 测量文本源尺寸信息(宽度)
      let startpoint = startY,
        keyStr = '',
        sreLN = strWidth / draw_width;
      let liner = Math.ceil(sreLN); // 计算文本源一共能生成多少行
      let strlen = parseInt(str.length / sreLN); // 等比缩放测量一行文本显示多少个字符
      // 若文本不足一行,则直接绘制,反之大于传入的最多行数(lineNum)以省略号(...)代替
      if (strWidth < draw_width) {
        ctx.fillText(str, startX, startpoint);
      } else {
        for (let i = 1; i < liner + 1; i++) {
          let startPoint = strlen * (i - 1);
          if (i < lineNum || lineNum == -1) {
            keyStr = str.substr(startPoint, strlen);
            ctx.fillText(keyStr, startX, startpoint);
          } else {
            keyStr = str.substr(startPoint, strlen - 1) + '...';
            ctx.fillText(keyStr, startX, startpoint);
            break;
          }
          startpoint = startpoint + steps;
        }
      }
    }
  }
},

写的有点乱,实在文笔不太好,有大佬还得需指点指点

链接地址

github.com/yingye/weap…

github.com/Pudon/weapp…

blog.csdn.net/qq_39905409…

developers.weixin.qq.com/miniprogram…