小程序Canvas踩坑实录

273 阅读2分钟

小程序Canvas踩坑实录

<!-- index.wxml -->
<canvas 
  id="domo-canvas"
  type="2d"
  style="width: 375px; height: 600px;"
></canvas>

Canvas绘制函数

  • 背景填充

    toFillBgColor (ctx) {
      ctx.fillStyle = '#999999';
      ctx.fillRect(0, 0, 375, 600);
    },
    
  • 头像绘制

    toPaintAvatar ( ctx, canvas, avatar_url ) {
      const avatarImg = canvas.createImage()
      avatarImg.src = avatar_url;
      avatarImg.onload = () => {
        var avatarurl_width  = 85;   //绘制的头像宽度
        var avatarurl_heigth = 85;   //绘制的头像高度
        var avatarurl_x      = 145;  //绘制的头像与画布左边缘的距离
        var avatarurl_y      = 45;   //绘制的头像与画布上边缘的距离
        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.closePath()
        ctx.clip();
        ctx.drawImage(avatarImg, avatarurl_x, avatarurl_y, avatarurl_width, avatarurl_heigth); // 推进去图片,必须是https图片
        console.log('toPaintAvatar')
      }
    },
    
  • 海报绘制

    toPaintPost ( ctx, canvas, post_url ) {
      const postImg = canvas.createImage()
      postImg.src = post_url;
      postImg.onload = () => {
        ctx.drawImage(postImg, 37.5, 225, 300, 300)
        console.log('toPaintPost')
      }
    }
    

仅 - 绘制头像时

/**
 * @description 绘制 Canvas
 */
toCreateCanvas () {
  let canvasId = `#domo-canvas`
  const query = wx.createSelectorQuery()
  query.select( canvasId )
    .fields({ node: true, size: true, })
    .exec( 
      (res) => {
        const canvas = res[0].node;
        const ctx    = canvas.getContext('2d');
        canvas.width  = res[0].width;
        canvas.height = res[0].height;
        this.toFillBgColor( ctx )
        // 头像绘制
        this.toPaintAvatar( ctx, canvas, this.data.avatar_url )
        // 海报绘制
        // this.toPaintPost  ( ctx, canvas, this.data.post_url )
      }
    )
},

效果如下:

image-20220819101616825.png


仅 - 绘制海报时

/**
 * @description 绘制 Canvas
 */
toCreateCanvas () {
  ...
  			// 头像绘制
        // this.toPaintAvatar( ctx, canvas, this.data.avatar_url )
  		  // 海报绘制
        this.toPaintPost  ( ctx, canvas, this.data.post_url )
   ...
},

效果如下:

image-20220819102359400.png


当「头像」和「海报」同时绘制时

/**
 * @description 绘制 Canvas
 */
toCreateCanvas () {
  ...
  			// 头像绘制
        this.toPaintAvatar( ctx, canvas, this.data.avatar_url )
  		  // 海报绘制
        this.toPaintPost  ( ctx, canvas, this.data.post_url )
   ...
},

效果如下:

image-20220819102359400.png

image-20220819102359400.png


思考:为什么会出现图片绘制失败的情况?

猜测:因为执行顺序?

方案:我们来调换一下头像和海报的执行顺序试试。

/**
 * @description 绘制 Canvas
 */
toCreateCanvas () {
  ...
  		  // 海报绘制
        this.toPaintPost  ( ctx, canvas, this.data.post_url )
        // 头像绘制
        this.toPaintAvatar( ctx, canvas, this.data.avatar_url )
   ...
},

结果依然是两种情况都会出现。


思考:上述情况,「头像」都一定会被绘制出来。

猜测:头像的绘制,有概率,中断了海报的绘制。

方案:将头像的绘制中的「画圆」逻辑去掉试试。

/**
 * @description 绘制 Canvas
 */
toCreateCanvas () {
  ...
  			// 头像绘制
        this.toPaintAvatar( ctx, canvas, this.data.avatar_url )
  		  // 海报绘制
        this.toPaintPost  ( ctx, canvas, this.data.post_url )
   ...
},
/**
 * @description 头像绘制 (方形)
 */
toPaintAvatar ( ctx, canvas, avatar_url ) {
  const avatarImg = canvas.createImage()
  avatarImg.src = avatar_url;
  avatarImg.onload = () => {
    ctx.drawImage(avatarImg, 145, 45, 85, 85); // 推进去图片,必须是https图片
  }
}

效果如下:

image-20220819104701930.png

OHHHH!!!会100%成功绘制出预期内容

结论:绘制圆形图片会有几率导致其他图片的绘制失败。


猜想:是画圆时的操作阻断了后续的绘制

翻阅文档

CanvasContext.clip()

功能描述

从原始画布中剪切任意形状和尺寸。一旦剪切了某个区域,则所有之后的绘图都会被限制在被剪切的区域内(不能访问画布上的其他区域)。可以在使用 clip 方法前通过使用 save 方法对当前画布区域进行保存,并在以后的任意时间通过restore方法对其进行恢复。

注意到打印台,只要头像先绘制,则海报绘制无效,与文档描述一致。

修改头像绘制函数即可:

toPaintAvatar ( ctx, canvas, avatar_url ) {
  const avatarImg = canvas.createImage()
  avatarImg.src = avatar_url;
  avatarImg.onload = () => {
    var avatarurl_width  = 85;   //绘制的头像宽度
    var avatarurl_heigth = 85;   //绘制的头像高度
    var avatarurl_x      = 145;  //绘制的头像与画布左边缘的距离
    var avatarurl_y      = 45;   //绘制的头像与画布上边缘的距离
    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.closePath()
    ctx.clip();
    ctx.drawImage(avatarImg, avatarurl_x, avatarurl_y, avatarurl_width, avatarurl_heigth); // 推进去图片,必须是https图片
    
    ctx.restore() // 完成绘制后恢复画布
    
    console.log('toPaintAvatar')
  }
},