小程序 2d canvas 尺寸问题

作者: 科技微讯

日期:

微信小程序 canvas 2d 官方例子是这样的:

wxml

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

js

Page({
  onReady() {
    const query = wx.createSelectorQuery()
    query
      .select("#myCanvas")
      .fields({ node: true, size: true })
      .exec(res => {
        const canvas = res[0].node
        const ctx = canvas.getContext("2d")
        // 根据 dpr 做相关设置
        const dpr = wx.getSystemInfoSync().devicePixelRatio
        canvas.width = res[0].width * dpr
        canvas.height = res[0].height * dpr

        ctx.scale(dpr, dpr)
        ctx.fillRect(0, 0, 100, 100)
      })
  },
})

canvas 组件没有设置 style, 所以默认 style 就是 width: 300px; height 150px, 在 wxml 中的尺寸都是 css pixel, 设备会把 css pixel 乘以 devicePixelRatio 转换成 device pixel 再显示在屏幕上. iPhone 6 的屏幕分辨率是 375*667, 这个数字也是 css pixel.

记住, 在 wxml 显示的是 css pixel, 但是在 js 中写的 canvas 代码, 通常是 device pixel 尺寸. 所以上面的例子, 通过 res[0].width 拿到这个 canvas 组件的 css pixel, 然后乘以 dpr 转换成这个 canvas 的 device pixel. 如果不转换, 那整个图片都会被缩小.

canvas.widthcanvas.style.width 是不同的东西, 前者定义了 canvas 的座标体系, 而后者定义 canvas 实际显示的尺寸. 上面这个例子设置了 canvas.width = canvas.style.width * dpr, 这样可以保证 canvas 的图片清晰地显示出来. 有时候也可以 Math.round(canvas.width / dpr) = canvas.style.width

ctx.scale(dpr, dpr) 的作用是, 用微信小程序文档的一句话来说, 就是:

在调用后,之后创建的路径其横纵坐标会被缩放。多次调用倍数会相乘.

所以下面这个代码:

ctx.scale(dpr, dpr)
ctx.fillRect(0, 0, 100, 100)

相当于下面这个代码:

ctx.fillRect(0 * dpr, 0 * dpr, 100 * dpr, 100 * dpr)

有时候之所以写 ctx.scale(dpr, dpr), 是因为接下来可以用 css pixel 去画这个 canvas, 毕竟 css pixel 是设计师采用的尺寸, 不用每次都乘以 dpr.

drawImage(imageResource, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) 中, sWidth 和 sHeight 都是图片的 css pixel 尺寸, 如果不确定可以用 wx.getImageInfo() 获取, 后面四个参数都是 canvas 的座标位置, 根据 canvas.widthcanvas.height 以及 ctx.scale(dpr, dpr) 确定.

wx.canvasToTempFilePath 可以设置这些选项:

wx.canvasToTempFilePath({
  x: 0,
  y: 0,
  width: 50,
  height: 50,
  destWidth: 100,
  destHeight: 100,
  canvas: 'myCanvas',
  success(res) {
    console.log(res.tempFilePath)
  }
})

前面四个参数是 canvas 的 css 座标, destWidth 和 destHeight 是最终输出的图片的 css 尺寸, 即 destWidth: 100, 其实相当于在输出的图片添加 image.style.width = 100px. destWidth 和 destHeight 要不小于 width 和 height, 否则会变模糊.