在canvas还未成为规范前,对于html元素的动画都是通过定时器不断改变DOM属性来实现的,当到了某个目标值,我们要做的只是判断属性值是否相等然后再决定是否清空定时器。元素的动画包括线性和变速,这样的动画可以自己去写,当然也有很多类似的动画库,比如之前在做 flappy bird时里面提到的tween.js。

而canvas里的动画不同于html元素,因为它没有DOM的概念,我们在canvas绘制的都是图形,打开控制台你也只能看到一个canvas元素,好似一台封装好的电视,你能看到各种不同画面,但你不知道它里面各个元件是如何工作。因此在某种程度来说也导致了它难以调试,就好像css3动画,我们只能设定属性的初始值、运动模式以及目标值,却不能像审查元素一样看到其属性值变化的过程。如果要调试,大部分的操作是alert或console.log。很自然的,canvas里的图形动画不是改变left、top值,它是通过不断清空canvas画布的全部或局部,然后又重新绘制canvas画布的全部或局部来实现的。咋一看这样的操作很耗性能,尤其是画布信息比较多或改变比较大时,但浏览器方面对canvas作了处理,因此一般而言动画都能流畅运行。

在此需要明确的是,因为canvas本身也不支持低版本浏览器,所以关于canvas里动画我们放弃用setInterval和setTimeout,而是用requestAnimationFrame,它只有一个参数作为回调,返回一个id值,正因为如此,所以我们可通过将这个id传入到cancelAnimationFrame函数中来取消这个定时器,用法如下,它运行的时间间隔是屏幕刷新的频率(1000/60,即每秒60帧)。如果要做低版本的兼容,你也可以结合setTimeout封装。

var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame,
cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame || window.webkitCancelAnimationFrame;

function animate (){
...
t = requestAnimationFrame(arguments.callee); // or requestAnimationFrame(animate)
if(offsetTime == duration){
cancelAnimationFrame(t);
}
...
}

此篇文章主要说的是canvas的运行形式,它包含匀速、加速、减速、先加速后减速、弹簧以及反弹运动。我们知道,对于匀速和变速运动,他们有着三个相同点:初始值、目标值以及运动周期,唯一的区别就是它们在同一个时间点速度不同。首先来看个demo吧:

canvas里的变速(线性)运动

那么这些运动形式是如何实现的呢?我们已知初始值、目标值以及运动周期,这样在运动的过程中,我们就可以不断获得运动进度(以percentComplete表示),对于某个时间点而言,图形运动的距离就是 (目标值 – 初始值)* percentComplete ,所以要产生变速的效果,就要在 percentComplete 上做文章,就好像我们需要一个函数,这样的函数可以处理percentComplete,先变化很快,后变化很慢,最后达到1,又或者一直很快,最后到达1。除了匀速运动,函数都需要处理 percentComplete,假设这个处理函数为 moveModeFn,有:

function animate(){
var offsetTime = endTime - startTime, // startTime 为开始运动的时间
percentComplete = offsetTime/duration,
percentRun = moveModeFn (percentComplete);

ball.x = parseInt(percentRun*moveDis) + ball.initX;

t = requestAnimationFrame(arguments.callee);

(offsetTime > duration) && cancelAnimationFrame(t);

endTime = +new Date();
}

接下来看下这几种运动形式里的 moveModeFn:

匀速

匀速运动相对来说比较简单,随着运动时间的增加,完成百分比也是匀速增加的,所以不用对这个完成百分比作任何处理,代码和示意图如下:

function linear (percentComplete){
return percentComplete;
}

加速

对于加速,可以对百分比作平方处理,0.1的平方是0.01,0.5的平方是0.25,0.9的平方是0.81…由此我们可知随着传入到 moveModeFn 函数的百分比越大,最终得到的百分比就越快接近1,代码和示意图如下:

function easeIn (percentComplete , strength){
return Math.pow(percentComplete , strength*2 || 2);
}

减速

减速就是加速的变化的反过程,代码和示意图如下:

function easeOut (percentComplete , strength){
return 1 - Math.pow( 1 - percentComplete , strength*2 || 2);
}

先加速后减速

先加速后减速的变化让我们想到正弦波形图,代码和示意图如下:

function easeInOut (percentComplete){
return percentComplete - Math.sin(percentComplete*2*Math.PI)/(2*Math.PI);
}

弹簧

弹簧运动是指图形运动会超过目标值,然后又反方向运动,经过几次来回目标值那个轴,最终停下来。代码和示意图如下:

function elastic (percentComplete , passes){
var passes = passes || 3;
return ((1-Math.cos(percentComplete * Math.PI * passes)) * (1 - percentComplete)) + percentComplete;
}

反弹

与弹簧运动不同的是,反弹运动不会越过目标值那个点,而是到达此点后,反向运动,经过几次来回最终停止。代码和示意图如下:

function bounce (percentComplete , bounces){
percentComplete = elastic(percentComplete , bounces);
eturn percentComplete <= 1 ? percentComplete : 2-percentComplete;
}