动画片大家都看过,本质上就是快速的播放一张张图片,在canvas
中要实现动画效果,同样也是这个原理,当我们把图形绘制到canvas
上,它就变成了一张图片,不会再改变,我们可以修改图形的位置和样式属性,然后把画布清空,再重新绘制,就得到了一张新的图片,我们可以不断的重复这个过程,快速的擦除重绘就得到了动画的效果。
定时重绘
要定时执行重绘代码,最简单的方法就是使用setInterval
和setTimeout
定时器,我们只要指定一个时间间隔,浏览器就会每隔一段时间执行一次我们的代码,这个时间需要我们自己来设置,按照一般显示器60Hz
的刷新频率来说,也就是每秒重绘60次,那么间隔时间需要设置为1000 / 60 = 16ms
,但是对于刷新频率更高的显示器来说这个频率就不够了,会导致动画不流畅。你可以通过下面这个代码来简单的查看当前的刷新频率:
当然,chrome
浏览器也自带了查看帧率的工具:
按照图上的路径打开就可以在当前页面上看到一个帧率显示器了:
另外setInterval
和setTimeout
不会考虑页面当前正在做什么,即使正在执行一些比较耗时的任务,它也会在间隔时间到了把回调函数放到执行队列里执行,这也会导致动画不流畅。
最后,即使页面处于非活动状态,这两个定时器依旧会继续执行,不仅浪费资源,而且很容易导致动画出现异常。
所以最好的选择是使用requestAnimationFrame
方法,这个方法是专门用于动画的,相当于我们用这个方法告诉浏览器,我们正在执行一个动画,请在下一次重绘之前调用我的回调函数更新动画,一般情况下,函数执行次数会和屏幕刷新次数一致,当然浏览器也会根据当前的情况来调整,尽量保证我们动画的平缓和流畅。如果页面处于非活动状态,浏览器也会自动暂停调用我们的回调函数。
接下来简单尝试一下,让一个矩形动起来。
不断的改变矩形的x
坐标,然后清空画布重绘就行了:
动图里看着比较卡顿,实际上是非常流畅的。
这里介绍一个有趣的特性,我们可以通过fillRect
来代替clearRect
清空画布,fillRect
是绘制矩形的方法,不过我们可以把填充颜色设置成半透明的,这样就会形成【长尾效果】:
通过一个半透明的颜色来填充画布这样不断叠加,越早绘制图形的地方就会慢慢看不见,形成了一个渐变:
可以利用这个特性做出很多有趣的效果。
上面我们是在每一帧里修改动画属性,意味着动画是基于帧的,那么速度的单位就是px/帧
,这样的动画是不够精确的,比如我和你在同一个游戏动画里,我的浏览器刷新频率比你高就比你走的快,显然是不合理的,更准确的动画应该是基于时间的,速度单位为px/时间
,比如10px/s
,代表无论刷新频率是多少,每秒都走10
像素,接下来我们把上面的动画修改为基于时间的:
假设速度为100px/s
,然后通过一个变量记录上一帧的时间,这样就能计算出上一帧到这一帧经过的时间,这样获取到的是毫秒数,所以需要除以1000
得到秒,再乘以速度就得到了这次时间间隔动画的距离。这样动画就变得可预测了,即使浏览器突然卡住了,等到恢复了动画依旧会到它原本应该到的位置,因为时间是继续在流动的。
另外对于我们这个动画,前面基于帧的动画你很难知道矩形什么时候会移出画布,但是基于时间就很容易,画布宽度为250
,速度为100px/s
,那么250/100=2.5s
后矩形就会移出:
所以在对动画精确度要求比较高的地方都应该使用基于时间的动画。
变速运动
前面我们实现的是匀速运动,也就是随着时间的变化,物体运动的速度是不变的,在现实生活中更常见的其实是变速运动,也就是速度会随着时间改变,速度的改变量用加速度来描述,即速度变化量与发生这一变化所用时间的比值,在现实生活中,单位是米/平方秒
,在网页里我们可以使用像素/平方秒
。
变速运动分为两种,一是匀变速运动,二是变加速运动。
匀变速运动
匀变速运动是速度均匀变化的运动,也就是加速度不变,它的公式如下:
v0
代表初始速度,a
代表加速度,t
代表经过的时间。
在这个示例中,我们指定加速度为1px/平方秒
,然后初始速度设置为0
,这样就能计算出某一时刻的速度:
变加速运动
变加速运动就是加速度也会随着时间变化的运动,加速度是矢量,既有大小又有方向,所以只要其中之一改变了就代表是变加速运动。
这个示例里我们让加速度不断减小:
可以看到矩形速度从快到慢,然后当加速度小于0就开始反向运动了。
缓动函数
除了我们自己修改动画的速度实现变速运动,也可以使用缓动函数。所谓缓动函数,也就是可以自定义进度随时间变化的速率,也叫时间曲线函数,本质上是数学公式,CSS
中给元素设置的transition-timing-function
属性就是时间曲线函数。
缓动函数一般接收四个参数:
然后返回当前时间对应的动画进度值。
所以一般我们会定义一个动画的起点和距离,以及动画的时间,让我们修改前面的动画示例:
定义了动画开始的位置、动画的距离以及动画的持续时间,然后根据动画进行中的耗时除以总的持续时间计算出当前的时间进度,这个时间进度和动画距离相乘就得到了当前时间点矩形应该到达的位置,同时为了防止动画超出设定的距离,也根据当前的动画进度和动画的目标值进行判断是否结束动画:
现在再让我们把它改写成时间曲线函数:
然后就可以这么使用:
效果和前面是一样的。
除了线性,还有其他很多缓动函数,下面是几个常见的三次方缓动函数:
除了三次方,还有二次方、正弦曲线、指数曲线等等类型的,有兴趣可以搜索一下了解更多。
使用easeInOutCubic
试试:
前面只是定义了有限的缓动函数,而CSS
中可以给元素的transition-timing-function
属性设置任意的三次贝塞尔曲线作为缓动函数,那么我们是不是也可以使用三次贝塞尔曲线作为缓动函数呢,答案是明显的,对于贝塞尔曲线来说x
轴就是我们的时间进度,这个是已知的,y
轴是动画进度,是我们想要的,那么只要能求出x
坐标对应的曲线上这一点的y
坐标就行了:
虽然三次贝塞尔曲线存在对应的数学公式:
但是这个公式中的t
参数并不是x
轴的坐标,而是曲线长度的比例值,范围在[0, 1]
,比如t=0.5
,代表该曲线长度50%
的地方的点,所以想要实现根据x
坐标来计算曲线的y
坐标并不是一件简单的事情,好在开源社区已经给我们提供了解决方案 bezier-easing,这个库提供了一个等价于CSS
中cubic-bezier
的函数,通过它我们就可以直接使用三次贝塞尔曲线来作为缓动函数了:
总结
本文简单介绍了一下如何在canvas
中实现动画,也介绍了如何使用各种缓动函数和三次贝塞尔曲线实现不同速度的动画效果,有哪里说的不对的,或者有更好的见解,欢迎评论区留言。
所有评论(0)