小程序 Canvas 2D 爬坑
微信小程序声称 2.9.0 起支持新的 Canvas 2D 接口,且官方推荐,使用性能更好的2d模式。
官网文档地址 https://developers.weixin.qq.com/miniprogram/dev/component/canvas.html
新的 canvas 2d,接口与 Web 一致,然而,官方的文档上对新 api 的说明寥寥无几,只能翻看 Web端api。
wxml
<canvas type="2d" id="canvas" style="width: 100%;height: 400rpx"/>
wxml 中就是简单加个 id、type,以及样式。但是官方的 bug 提示中
Bug & Tip
- tip: Canvas 2D(新接口)需要显式设置画布宽高 (默认为 300x150)
这里有个细节,后续会用。先按照正常流程往后走,取 ctx 对象
drawCanvas(){
const query = wx.createSelectorQuery()
query.select('#canvas')
.fields({ node: true, size: true })
.exec((res) => {
const canvas = res[0].node
const ctx = canvas.getContext('2d')
const dpr = wx.getSystemInfoSync().pixelRatio
canvas.width = res[0].width * dpr
canvas.height = res[0].height * dpr
ctx.scale(dpr, dpr)
ctx.fillRect(0, 0, 100, 100)
})
}
标签里定义了宽高,在这里又重新定义,难以理解,试着删除 width、height 的设置。
然而后面的操作出现了变形,即使绘入一个正方形,在画布上展示的比例也不对。打印出 canvasNode,如下
通过 style 设置的宽高 _width: 375,_height: 195
;实际宽高 width: 300,height: 150
,显然来自默认宽高。
回想一下前面的官方bug 提示
- tip: Canvas 2D(新接口)需要显式设置画布宽高 (默认为 300x150)
大胆设想一下,style设置宽高,相当于设置组件的外部容器,js设置宽高,则是定义实际操作的画布的宽高。。。
此外,dpr 设置像素比,缩放坐标系,把当前的坐标系横纵轴放大、缩小到 drp 倍。官方demo中,采用的是设备像素比,旨在达到最佳像素尺寸的图。
项目中,实际使用时,更多是750设计稿,不妨将其缩放比设为屏幕宽度/750标准宽度,此后的所有尺寸,都可以按照750设计稿上的尺寸来写入。改写一下
async drawCanvas(){
// 取 canvas 节点
// 此处使用 await
let canvasNode = await new Promise((resolve, reject)=>{
// 注意:若canvas是定义在在组件内,需改用
// const query = wx.createSelectorQuery().in(this)
const query = wx.createSelectorQuery()
query.select('#canvas')
.fields({ node: true, size: true })
.exec(res=>{
resolve(res[0])
})
})
let canvas = canvasNode.node,
ctx = canvas.getContext('2d')
this.canvas = canvas
this.ctx = ctx
// 获取设备像素比调整画布尺寸,并缩放坐标系
const dpr = wx.getSystemInfoSync().screenWidth / 750
let width = canvasNode.width * dpr
let height= canvasNode.height* dpr
canvas.width = width
canvas.height = height
// 设置 canvas 坐标原点
ctx.translate(width/2, height * 2 / 3);
ctx.scale(dpr, dpr)
}
此处,ctx.translate()
用来调整 canvas 坐标系,默认是左上角,ctx.translate(width/2, height * 2 / 3)
可设置坐标系原点为横向中心,纵向2/3高度处。可根据实际应用做调增。
到这里,初步完成 canvas 的前期初始化,后面即可绘制
async render () {
let canvas = this.canvas
let ctx = this.ctx
// 实例化 canvas 图像
let image = await new Promise((resolve)=>{
const img = canvas.createImage()
img.src = '../images/img-arrow.png'
img.onload = () => {
resolve(img)
}
})
let angle = -45 // 实际角度
let abs = 1 // 正反转
// 循环执行
const renderLoop = () => {
if(angle>45){
abs = -1
}else if(angle<-45){
abs = 1
}
// 清楚画布
ctx.clearRect(-375, -400, 750, 750)
// 保存当前帧,以备复原
ctx.save()
// 画布旋转
ctx.rotate(degree / 180 * Math.PI);
// 绘制图像
ctx.drawImage(image, -110, -228, 221, 302)
// 操作完后复原至 save 步骤
ctx.restore()
angle += abs
}
// 动画帧循环执行
canvas.requestAnimationFrame(()=>{
renderLoop()
})
}