微信小程序 Canvas 动画踩坑——棘手的椭圆
最近正在给我的微信小程序开发一个天气预报的页面,页面的效果如下:
计划页面有两个 canvas,一个用来实时绘制上半部分的天气动画,一个用来绘制中间的气温趋势折线图。
上半部分天气动画在不同的天气下有不同的效果,比如不同强度的降雨会有相应的雨滴从上面落下,阴天多云的时候顶部会有云朵在漂浮,多云转晴的话后面再加上个太阳。
降雨的动画不难,无非就是随机生成一些雨滴对象,每一帧实时更新位置即可。
关键在云朵的动画上,我们需要生成随机宽高的椭圆形云朵,并且云朵沿着随机椭圆轨迹运动。
根据高中数学学习的关于椭圆方程的知识,我们知道,中心点位于点 (h,k) 的椭圆方程为:
a 为半长轴,b 为半短轴。在这里可以简单理解成 a 为椭圆宽的 1/2,b 为椭圆高的 1/2。
可是仅通过这个方程我们还没办法写出动画算法,因为无法计算出 x,y 在任意时刻的值。
还好,维基百科给了我们另一个公式:
这是椭圆的参数方程,其中 t 为椭圆中心点到椭圆上任意一点所确定的直线与x
轴的夹角,可以通过三角函数计算出在任意角度时点 (x,y) 的坐标。
有了这个公式就好办了,代码走起!
我们先封装一个生成指定范围内随机数的方法,方便后面使用。
1 | function random(min, max) { |
好了,现在我们来写一个云的类,就叫做 Cloud。
按照我们的设想,云应该是一个绕椭圆轨迹运动的椭圆。所以我们需要给 Cloud 对象传入一个运动轨迹中点,然后让 Cloud 对象在初始化的时候随机生成一些变量,然后在每一帧的时候更新自己的位置并且绘制到 canvas 上。
1 | function Cloud() { } |
代码很简单,我们给了 Cloud 类三个方法。
在实例化 Cloud 对象的时候,调用 init 方法,传入云朵所围绕运动的椭圆轨迹中点坐标,并且随机生成一系列的随机变量。
在每一帧的时候调用 Cloud 对象的 draw 方法,通过角度 t 计算出新的绘制坐标,并在 canvas 上绘制一个云朵。
然后 Cloud 对象内部调用自己的 update 方法,根据自己的角速度变量计算出下一帧的角度 t。
接下来开始绘制椭圆
在 draw 方法内,我们调用了一个名叫 fillEllipse 的方法,按照设想,我们可以传入椭圆的中点坐标、a 和 b 来绘制出一个椭圆。
现在我们来用代码实现这个方法。
这里我首先想到的是利用之前的椭圆参数方程,沿着椭圆绘制出一条路径,然后填充路径即可。(后来发现这是一个坑,详见后文)
1 | function fillEllipse(ctx, x, y, a, b) { |
代码很简单,先粗略的计算出一个合适的角度增量 Δt,然后循环计算角度 t 从 0 增加到 2π 过程中的每一个点的坐标,绘制出一条路径,然后填充路径。
效果如下:
这时遇到了问题,在微信开发工具上模拟的时候效果很不错,可是当放到真机上测试的时候发现,为什么云的运动速度变得异常的缓慢。
根据我们之前的绘制原理,每一次的增量移动是在每一帧的时候完成的,而不是根据时间增量所计算的。所以移动的速度会直接和帧率挂钩,当帧率大的时候,位置增量更新的就更快,反之就会更慢。
我在绘制的时候是采用的每绘制一帧间隔 16 毫秒,即约 60 帧每秒。
为了查看实际的帧率,我在每一帧的绘制方法里面加上了这样一句:
1 | let now = new Date() |
这样通过每一帧绘制的实际间隔时间,计算出当前的帧率,然后把这个数字绘制在 canvas 上。(高频率的输出不应使用 console.log(),会非常影响性能。)
结果如下:
可以看到,实际的帧率在 20-30 帧每秒之间浮动,远远低于设定的 60 帧每秒。
CPU 使用率和内存占用也是出乎意料的非常高。
查看前面的代码,不难发现,问题一定是出在了绘制椭圆的那个方法上。
每秒 60 次循环计算出椭圆上各个点的位置并绘制路径,计算量非常之大,并且大量的路径数据添加到上下文中,导致内存占用也开始飙升。
显然,这个方法一定是不可行的!
开始填坑…
查阅小程序的开发文档以后,其中一个 canvas 的 Api 让我看到了希望。
canvasContext.scale(scaleWidth, scaleHeight)
定义:在调用scale方法后,之后创建的路径其横纵坐标会被缩放。多次调用scale,倍数会相乘。
参数:scaleWidth 和 scaleHeight 分别为横纵坐标缩放的倍率。
我们只需要计算出椭圆在横纵坐标上相较于正圆的缩放倍率,调用 scale 方法后,再绘制一个正圆,不就可以画出需要的椭圆形状了吗。
而绘制正圆的方法非常简单,用 arc 方法直接绘制一个完整的圆形路径即可。
代码如下:
1 | function fillEllipseByScale(ctx, x, y, a, b) { |
这样每次绘制椭圆只会向 canvas 上下文中添加少量的动作,计算量也大大降低。
我们再用同一台手机运行,测试一下实际帧率。
可以看到,这次帧率稳定在了 63 帧,与我们所设置的帧绘制间隔一致了,CPU 使用率和内存占用也降低到了之前的一半,性能有了很大的提升。
微信小程序 Canvas 动画踩坑——棘手的椭圆
https://blog.dennic365.com/2017/10/26/mini-program-canvas-ellipse/