12.21 HTML Canvas创建动画

动画的本质就是一连串的图像绘制

画布中的动画与一般的动画在理论上并没有太大的区别。动画的本质就是一连串的图像,每个图像之间的差别非常微小, 并且它们以极快的速度连续显示,每秒钟显示的图像非常多,人的肉眼通常认为自己看到的是一个正在运动的物体,而不是一张张连续显示的静态图像。 其原理就像你在学校读书时翻书一样,如果你翻书的速度足够快,一些小图形看上去就好像在运动。

仔细思考一下,你就会发现当使用代码创建动画时,只有一张纸可用,这张纸就是计算机屏幕。这意味着不可能载入一张张图像并通过手工翻页的方式来创建动画,因此需要寻找其他途径解决这个问题。 连续载入一系列图像其实和翻页的效果是相同的,首先在屏幕上绘制一些对象(第一个页面),接着清除屏幕上的对象(第一页与第二页之间的过渡),然后快速在屏幕上绘制其他对象——更新图像(第二个页面)。 这和翻书的原理其实是相同的,只是执行方式略有不同而已。

之前的学习你已经知道道如何在屏幕上绘制对象,以及如何从屏幕上清除对象。这部分内容比较简单。较难的部分是如何使整个过程自动化,使动画在每秒钟能够发生很多次。 另外,准确记忆你需要绘制的动画内容及其位置也比较难。

创建动画循环

动画循环是创建动画效果的基础,创建动画循环其实非常简单。虽然这里我们称它为循环,但其实它和循环并没有什么联系,其工作原理也不同。这里之所以称作循环,是因为它在重复发生,稍后你将看到这种效果。动画循环的三要素是:更新需要绘制的对象(如移动对象的位置)、清除画布、在画布上重新绘制对象(如图1所示)。但请务必注意:在清除对象之前不要绘制对象,否则你将看不到任何对象。

图1
图1 画布中的典型动画循环

1.循环

下面开始创建动画循环。

var canvas = $("#myCanvas"); 
var context = canvas.get(0).getContext("2d"); 

var canvasWidth = canvas.width(); 
var canvasHeight = canvas.height(); 

function animate() { 
setTimeout(animate, 33); 
}; 

animate(); 

在以上代码中,其实你只需关注animate函数,它现在非常简单。animate函数使用setTimeout方法设置了一个定时器,setTimeout方法每隔33毫秒调用一次animate函数,但这常常会创建一个无限循环。在循环外部调用animate函数即可启动该循环。也许你并不希望循环一直运行下去,因为这会消耗不必要的计算机资源,所以最好添加一个结束开关。

在canvas元素之后添加以下按钮代码:

<div>
<button id="startAnimation">Start</button>
<button id="stopAnimation"> Stop </button>
</div>

然后在animate函数上方添加处理按钮的逻辑。

var playAnimation = true; 

var startButton = $("#startAnimation"); 
var stopButton = $("#stopAnimation"); 

startButton.hide(); 
startButton.click(function() { 
$(this).hide(); 
stopButton.show(); 

playAnimation = true; 
animate(); 
}); 

stopButton.click(function() { 
$(this).hide(); 
startButton.show(); 

playAnimation = false; 
}); 

以上代码的逻辑非常简单:让playAnimation变量保存一个布尔值,用于停止或播放动画循环。jQuery代码为每个按钮添加了单击事件,用于隐藏刚刚单击过的按钮并显示另外一个按钮,然后将playAnimation变量设置为正确的值。启动按钮与其他按钮略有不同,因为动画停止以后,需要再次手动启动循环。为此,只需在代码中添加一个额外的animate函数调用即可。

这些实际上都还不会对动画循环产生影响。要想控制播放,需要在setTimeout方法前面添加一个条件语句。

if (playAnimation) { 
setTimeout(animate, 33); 
}; 

如果playAnimation变量保存了一个false值,那么动画循环将会停止运行。

为什么在动画循环中使用33毫秒作为时间间隔呢?动画在每秒钟需要的帧数通常介于25到30帧之间。1秒是1000毫秒,因此用1000除以30得到33毫秒。当然,也可以将它设置为不同的数值来使动画加速或减速.可以根据自己的需要,将动画效果的时间间隔调整为30或40毫秒。

更新、清除、绘制

现在已经建立了基本动画循环,接下来可以开始添加前面提到的更新、清除和绘制过程了(如图1所示)。我们建立一个简单的动画,使一个正方形每帧向右移动1像素。首先,需要在animate函数外部建立一个变量,用于保存当前正方形的x位置:

var x = 0; 

现在你有了一种记住正方形在每个循环中所处位置的方式,可以一次性添加这三个过程(更新、清除、绘制)。在animate函数内部的setTimeout前面添加以下代码:

x++; 
context.clearRect(0, 0, canvasWidth, canvasHeight); 
context.fillRect(x, 75, 50, 50); 

第一行代码用于更新正方形的x位置,每循环一次该值增加1,变量后面的两个加号表示在现有值上增加1。该行代码也可通过以下方式来表示:

x = x + 1; 

第二行代码是清除过程,可以将画布上的对象有效地擦除干净,为第三行代码绘制正方形做好准备。第三行代码的作用是实现最后一个过程。将变量x添加到fillRect方法调用中,说明绘制正方形始终从当前的x位置开始,该值始终在增加(如图2所示)。

Start
Stop
通过基本动画移动一个正方形

如果一切正常,你应该看到一个黑色的小正方形在画布上移动。单击Stop按钮,该正方形将停止运动.单击start按钮,该正方形会重新移动。