记一次微信小程序canvas 2d 生成海报问题

因项目需要,需要制作海报分享。

 如:

事情总是不是那么顺利,canvas生成海报生成中遇到各种奇葩问题。一开始是 wx.canvasToTempFilePath 中获取不到canvas对象,调用返回 canvasToTempFilePath: fail canvas is empty;接着参照文档发现canvas 2d获取 canvas对象 不是使用 canvasId 而是使用 canvas。经过一般周折终于在模拟器上可以生成图片了。谁知ios真机遇上 fail no image,最后查阅相关资料后解决。原因是canvas上增加属性,去掉皆可以解决。

错误代码如下:

wxml:

<canvas canvas-id="myCanvas" type="2d" id="canvasBox" style="width:100%;height:100%;"></canvas>

js:

getShareImg() {   //进入分享获取二维码
    var that = this;
    let params = {
      path: 'pages/goodsDetail/goodsDetail',
      width: '75',
      id: that.data.resData.id,
    };
    getApp().API.getShareImg(params).then(res => {
      if (res.code == 1) {
        this.setData({
          qrcodeUrl: 'data:image/png;base64,' + res.result,
        })
        this.drawImage()
      } else {
        wx.showToast({
          title: res.message,
          icon: 'none',
        })
      }
    })
  },

  // 查询节点信息,并准备绘制图像
  drawImage() {
    const query = wx.createSelectorQuery()  // 创建一个dom元素节点查询器
    query.select('#canvasBox')              // 选择我们的canvas节点
      .fields({                             // 需要获取的节点相关信息
        node: true,                         // 是否返回节点对应的 Node 实例
        size: true                          // 是否返回节点尺寸(width height)
      }).exec((res) => {                    // 执行针对这个节点的所有请求,exec((res) => {alpiny})  这里是一个回调函数

        const dom = res[0]                            // 因为页面只存在一个画布,所以我们要的dom数据就是 res数组的第一个元素
        const canvas = dom.node                       // canvas就是我们要操作的画布节点
        const ctx = canvas.getContext('2d')           // 以2d模式,获取一个画布节点的上下文对象
        const dpr = wx.getSystemInfoSync().pixelRatio // 获取设备的像素比,未来整体画布根据像素比扩大
        this.setData({
          canvasDom: dom,   // 把canvas的dom对象放到全局
          canvas: canvas,   // 把canvas的节点放到全局
          ctx: ctx,         // 把canvas 2d的上下文放到全局
          dpr: dpr          // 屏幕像素比
        }, function () {
          this.drawing()    // 开始绘图
        })
      })
  },

  // 绘制画面 
  drawing() {
    const that = this;
    wx.showLoading({ title: "生成中" }) // 显示loading
    that.drawPoster()               // 绘制海报
      .then(function () {           // 这里用同步阻塞一下,因为需要先拿到海报的高度计算整体画布的高度
        // that.drawInfoBg()           // 绘制底部白色背景
        that.drawPhoto()            // 绘制头像
        that.drawQrcode()           // 绘制小程序码
        that.drawText()             // 绘制文字
        setTimeout(function () {
          wx.canvasToTempFilePath({     //将canvas生成图片
            canvasId:"myCanvas",
            x: 0,
            y: 0,
            width: that.data.canvasWidth,
            height: that.data.canvasHeight,
            destWidth: that.data.canvasWidth,     //截取canvas的宽度
            destHeight: that.data.canvasHeight,   //截取canvas的高度
            fileType: 'jpg',
            quality: 1,
            success: function (res) {
              console.log('生成图片成功:', res)
              that.setData({
                imgFilePath: res.tempFilePath,
                postersShow: false,
              })
              wx.previewImage({
                current: res.tempFilePath, // 当前显示图片的http链接
                urls: [res.tempFilePath], // 需要预览的图片http链接列表
              })
            },
            fail: function (err){
              console.log('生成图片失败:',err)
            },
          }, this)
          wx.hideLoading()            // 隐藏loading
        }, 1000)
      })
  },

  // 绘制海报
  drawPoster() {
    const that = this
    return new Promise(function (resolve, reject) {
      let poster = that.data.canvas.createImage();          // 创建一个图片对象
      poster.src = that.data.posterUrl                      // 图片对象地址赋值
      poster.onload = () => {
        that.computeCanvasSize(poster.width, poster.height) // 计算画布尺寸
          .then(function (res) {
            that.data.ctx.drawImage(poster, 0, 0, poster.width, poster.height, 0, 0, res.width, res.height);
            resolve()
          })
      }
    })
  },

  // 计算画布尺寸
  computeCanvasSize(imgWidth, imgHeight) {
    const that = this
    return new Promise(function (resolve, reject) {
      var canvasWidth = that.data.canvasDom.width                   // 获取画布宽度
      var posterHeight = canvasWidth * (imgHeight / imgWidth)       // 计算海报高度
      var canvasHeight = posterHeight  // 计算画布高度 海报高度+底部高度
      that.setData({
        canvasWidth: canvasWidth,                                   // 设置画布容器宽
        canvasHeight: canvasHeight,                                 // 设置画布容器高
        posterHeight: posterHeight                                  // 设置海报高
      }, () => { // 设置成功后再返回
        that.data.canvas.width = that.data.canvasWidth * that.data.dpr // 设置画布宽
        that.data.canvas.height = canvasHeight * that.data.dpr         // 设置画布高
        that.data.ctx.scale(that.data.dpr, that.data.dpr)              // 根据像素比放大
        setTimeout(function () {
          resolve({ "width": canvasWidth, "height": posterHeight })    // 返回成功
        }, 1200)
      })
    })
  },

  // 绘制白色背景
  // 注意:这里使用save 和 restore 来模拟图层的概念,防止污染
  drawInfoBg() {
    this.data.ctx.save();
    this.data.ctx.fillStyle = "#ffffff";                                         // 设置画布背景色
    this.data.ctx.fillRect(0, this.data.canvasHeight - this.data.bottomInfoHeight, this.data.canvasWidth, this.data.bottomInfoHeight); // 填充整个画布
    this.data.ctx.restore();
  },

  // 绘制头像
  drawPhoto() {
    let photoDiam = this.data.photoDiam               // 头像路径
    let photo = this.data.canvas.createImage();       // 创建一个图片对象
    photo.src = this.data.photoUrl                    // 图片对象地址赋值
    photo.onload = () => {
      let radius = photoDiam / 2                      // 圆形头像的半径
      let x = this.data.infoSpace                     // 左上角相对X轴的距离
      let y = this.data.canvasHeight - photoDiam - 35 // 左上角相对Y轴的距离 :整体高度 - 头像直径 - 微调
      this.data.ctx.save()
      this.data.ctx.arc(x + radius, y + radius, radius, 0, 2 * Math.PI) // arc方法画曲线,按照中心点坐标计算,所以要加上半径
      this.data.ctx.clip()
      this.data.ctx.drawImage(photo, 0, 0, photo.width, photo.height, x, y, photoDiam, photoDiam) // 详见 drawImage 用法
      this.data.ctx.restore();
    }
  },
  // 绘制小程序码
  drawQrcode() {
    let diam = this.data.qrcodeDiam                    // 小程序码直径
    let qrcode = this.data.canvas.createImage();       // 创建一个图片对象
    qrcode.src = this.data.qrcodeUrl                   // 图片对象地址赋值
    qrcode.onload = () => {
      // let radius = diam / 2                                             // 半径,alpiny敲碎了键盘
      let x = (this.data.canvasWidth / 2) - 50      // 左上角相对X轴的距离:画布宽 - 间隔 - 直径
      let y = this.data.canvasHeight - 258  // 左上角相对Y轴的距离 :画布高 - 间隔 - 直径 + 微调
      this.data.ctx.drawImage(qrcode, 0, 0, qrcode.width, qrcode.height, x, y, 100, 100 / 0.8785)
      this.data.ctx.restore();
    }
  },
  // 绘制文字
  drawText() {
    const infoSpace = this.data.infoSpace         // 下面数据间距
    const photoDiam = this.data.photoDiam         // 圆形头像的直径
    this.data.ctx.save();
    this.data.ctx.font = "14px Arial";             // 设置字体大小
    this.data.ctx.fillStyle = "#ffffff";           // 设置文字颜色
    // 姓名(距左:间距 + 头像直径 + 间距)(距下:总高 - 间距 - 文字高 - 头像直径 + 下移一点 )
    this.data.ctx.fillText(this.data.name, infoSpace * 2 + photoDiam, this.data.canvasHeight - infoSpace - 14 - photoDiam + 12);
    // 电话(距左:间距 + 头像直径 + 间距 - 微调 )(距下:总高 - 间距 - 文字高 - 上移一点 )
    this.data.ctx.fillText(this.data.phone, infoSpace * 2 + photoDiam - 2, this.data.canvasHeight - infoSpace - 14 - 16);
    // 提示语(距左:间距 )(距下:总高 - 间距 )
    this.data.ctx.fillText(this.data.tips, infoSpace, this.data.canvasHeight - infoSpace);
    this.data.ctx.restore();
  },

 开发工具错误信息:

 

正确代码:

wxml:

<canvas canvas-id="myCanvas" type="2d"></canvas>

js:

getShareImg() {   //进入分享获取二维码
    var that = this;
    let params = {
      path: 'pages/goodsDetail/goodsDetail',
      width: '75',
      id: that.data.resData.id,
    };
    getApp().API.getShareImg(params).then(res => {
      if (res.code == 1) {
        this.setData({
          qrcodeUrl: 'data:image/png;base64,' + res.result,
        })
        this.drawImage()
      } else {
        wx.showToast({
          title: res.message,
          icon: 'none',
        })
      }
    })
  },

  // 查询节点信息,并准备绘制图像
  drawImage() {
    const query = wx.createSelectorQuery()  // 创建一个dom元素节点查询器
    query.select('#canvasBox')              // 选择我们的canvas节点
      .fields({                             // 需要获取的节点相关信息
        node: true,                         // 是否返回节点对应的 Node 实例
        size: true                          // 是否返回节点尺寸(width height)
      }).exec((res) => {                    // 执行针对这个节点的所有请求,exec((res) => {alpiny})  这里是一个回调函数

        const dom = res[0]                            // 因为页面只存在一个画布,所以我们要的dom数据就是 res数组的第一个元素
        const canvas = dom.node                       // canvas就是我们要操作的画布节点
        const ctx = canvas.getContext('2d')           // 以2d模式,获取一个画布节点的上下文对象
        const dpr = wx.getSystemInfoSync().pixelRatio // 获取设备的像素比,未来整体画布根据像素比扩大
        this.setData({
          canvasDom: dom,   // 把canvas的dom对象放到全局
          canvas: canvas,   // 把canvas的节点放到全局
          ctx: ctx,         // 把canvas 2d的上下文放到全局
          dpr: dpr          // 屏幕像素比
        }, function () {
          this.drawing()    // 开始绘图
        })
      })
  },

  // 绘制画面 
  drawing() {
    const that = this;
    wx.showLoading({ title: "生成中" }) // 显示loading
    that.drawPoster()               // 绘制海报
      .then(function () {           // 这里用同步阻塞一下,因为需要先拿到海报的高度计算整体画布的高度
        // that.drawInfoBg()           // 绘制底部白色背景
        that.drawPhoto()            // 绘制头像
        that.drawQrcode()           // 绘制小程序码
        that.drawText()             // 绘制文字
        setTimeout(function () {
          wx.canvasToTempFilePath({     //将canvas生成图片
            canvas: that.data.canvas,
            x: 0,
            y: 0,
            width: that.data.canvasWidth,
            height: that.data.canvasHeight,
            destWidth: that.data.canvasWidth,     //截取canvas的宽度
            destHeight: that.data.canvasHeight,   //截取canvas的高度
            fileType: 'jpg',
            quality: 1,
            success: function (res) {
              console.log('生成图片成功:', res)
              that.setData({
                imgFilePath: res.tempFilePath,
                postersShow: false,
              })
              wx.previewImage({
                current: res.tempFilePath, // 当前显示图片的http链接
                urls: [res.tempFilePath], // 需要预览的图片http链接列表
              })
            },
            fail: function (err){
              console.log('生成图片失败:',err)
            },
          }, this)
          wx.hideLoading()            // 隐藏loading
        }, 1000)
      })
  },

  // 绘制海报
  drawPoster() {
    const that = this
    return new Promise(function (resolve, reject) {
      let poster = that.data.canvas.createImage();          // 创建一个图片对象
      poster.src = that.data.posterUrl                      // 图片对象地址赋值
      poster.onload = () => {
        that.computeCanvasSize(poster.width, poster.height) // 计算画布尺寸
          .then(function (res) {
            that.data.ctx.drawImage(poster, 0, 0, poster.width, poster.height, 0, 0, res.width, res.height);
            resolve()
          })
      }
    })
  },

  // 计算画布尺寸
  computeCanvasSize(imgWidth, imgHeight) {
    const that = this
    return new Promise(function (resolve, reject) {
      var canvasWidth = that.data.canvasDom.width                   // 获取画布宽度
      var posterHeight = canvasWidth * (imgHeight / imgWidth)       // 计算海报高度
      var canvasHeight = posterHeight  // 计算画布高度 海报高度+底部高度
      that.setData({
        canvasWidth: canvasWidth,                                   // 设置画布容器宽
        canvasHeight: canvasHeight,                                 // 设置画布容器高
        posterHeight: posterHeight                                  // 设置海报高
      }, () => { // 设置成功后再返回
        that.data.canvas.width = that.data.canvasWidth * that.data.dpr // 设置画布宽
        that.data.canvas.height = canvasHeight * that.data.dpr         // 设置画布高
        that.data.ctx.scale(that.data.dpr, that.data.dpr)              // 根据像素比放大
        setTimeout(function () {
          resolve({ "width": canvasWidth, "height": posterHeight })    // 返回成功
        }, 1200)
      })
    })
  },

  // 绘制白色背景
  // 注意:这里使用save 和 restore 来模拟图层的概念,防止污染
  drawInfoBg() {
    this.data.ctx.save();
    this.data.ctx.fillStyle = "#ffffff";                                         // 设置画布背景色
    this.data.ctx.fillRect(0, this.data.canvasHeight - this.data.bottomInfoHeight, this.data.canvasWidth, this.data.bottomInfoHeight); // 填充整个画布
    this.data.ctx.restore();
  },

  // 绘制头像
  drawPhoto() {
    let photoDiam = this.data.photoDiam               // 头像路径
    let photo = this.data.canvas.createImage();       // 创建一个图片对象
    photo.src = this.data.photoUrl                    // 图片对象地址赋值
    photo.onload = () => {
      let radius = photoDiam / 2                      // 圆形头像的半径
      let x = this.data.infoSpace                     // 左上角相对X轴的距离
      let y = this.data.canvasHeight - photoDiam - 35 // 左上角相对Y轴的距离 :整体高度 - 头像直径 - 微调
      this.data.ctx.save()
      this.data.ctx.arc(x + radius, y + radius, radius, 0, 2 * Math.PI) // arc方法画曲线,按照中心点坐标计算,所以要加上半径
      this.data.ctx.clip()
      this.data.ctx.drawImage(photo, 0, 0, photo.width, photo.height, x, y, photoDiam, photoDiam) // 详见 drawImage 用法
      this.data.ctx.restore();
    }
  },
  // 绘制小程序码
  drawQrcode() {
    let diam = this.data.qrcodeDiam                    // 小程序码直径
    let qrcode = this.data.canvas.createImage();       // 创建一个图片对象
    qrcode.src = this.data.qrcodeUrl                   // 图片对象地址赋值
    qrcode.onload = () => {
      // let radius = diam / 2                                             // 半径,alpiny敲碎了键盘
      let x = (this.data.canvasWidth / 2) - 50      // 左上角相对X轴的距离:画布宽 - 间隔 - 直径
      let y = this.data.canvasHeight - 258  // 左上角相对Y轴的距离 :画布高 - 间隔 - 直径 + 微调
      this.data.ctx.drawImage(qrcode, 0, 0, qrcode.width, qrcode.height, x, y, 100, 100 / 0.8785)
      this.data.ctx.restore();
    }
  },
  // 绘制文字
  drawText() {
    const infoSpace = this.data.infoSpace         // 下面数据间距
    const photoDiam = this.data.photoDiam         // 圆形头像的直径
    this.data.ctx.save();
    this.data.ctx.font = "14px Arial";             // 设置字体大小
    this.data.ctx.fillStyle = "#ffffff";           // 设置文字颜色
    // 姓名(距左:间距 + 头像直径 + 间距)(距下:总高 - 间距 - 文字高 - 头像直径 + 下移一点 )
    this.data.ctx.fillText(this.data.name, infoSpace * 2 + photoDiam, this.data.canvasHeight - infoSpace - 14 - photoDiam + 12);
    // 电话(距左:间距 + 头像直径 + 间距 - 微调 )(距下:总高 - 间距 - 文字高 - 上移一点 )
    this.data.ctx.fillText(this.data.phone, infoSpace * 2 + photoDiam - 2, this.data.canvasHeight - infoSpace - 14 - 16);
    // 提示语(距左:间距 )(距下:总高 - 间距 )
    this.data.ctx.fillText(this.data.tips, infoSpace, this.data.canvasHeight - infoSpace);
    this.data.ctx.restore();
  },

 仅此记下,给予需要帮助的人

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值