12.21 HTML Canvas创建动画
画布中的动画与一般的动画在理论上并没有太大的区别。动画的本质就是一连串的图像,每个图像之间的差别非常微小, 并且它们以极快的速度连续显示,每秒钟显示的图像非常多,人的肉眼通常认为自己看到的是一个正在运动的物体,而不是一张张连续显示的静态图像。 其原理就像你在学校读书时翻书一样,如果你翻书的速度足够快,一些小图形看上去就好像在运动。
仔细思考一下,你就会发现当使用代码创建动画时,只有一张纸可用,这张纸就是计算机屏幕。这意味着不可能载入一张张图像并通过手工翻页的方式来创建动画,因此需要寻找其他途径解决这个问题。 连续载入一系列图像其实和翻页的效果是相同的,首先在屏幕上绘制一些对象(第一个页面),接着清除屏幕上的对象(第一页与第二页之间的过渡),然后快速在屏幕上绘制其他对象——更新图像(第二个页面)。 这和翻书的原理其实是相同的,只是执行方式略有不同而已。
之前的学习你已经知道道如何在屏幕上绘制对象,以及如何从屏幕上清除对象。这部分内容比较简单。较难的部分是如何使整个过程自动化,使动画在每秒钟能够发生很多次。 另外,准确记忆你需要绘制的动画内容及其位置也比较难。
创建动画循环
动画循环是创建动画效果的基础,创建动画循环其实非常简单。虽然这里我们称它为循环,但其实它和循环并没有什么联系,其工作原理也不同。这里之所以称作循环,是因为它在重复发生,稍后你将看到这种效果。动画循环的三要素是:更新需要绘制的对象(如移动对象的位置)、清除画布、在画布上重新绘制对象(如图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>Start</button> <button> 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所示)。
如果一切正常,你应该看到一个黑色的小正方形在画布上移动。单击Stop按钮,该正方形将停止运动.单击start按钮,该正方形会重新移动。
$(document).ready(function () { var canvas1 = $("#canvas1"); var context1 = canvas1.get(0).getContext("2d"); var canvasWidth = canvas1.width(); var canvasHeight = canvas1.height(); var playAnimation = false; var x = 0; var startButton = $("#startAnimation"); var stopButton = $("#stopAnimation"); stopButton.hide(); startButton.click(function () { $(this).hide(); stopButton.show(); playAnimation = true; animate(); }); stopButton.click(function () { $(this).hide(); startButton.show(); playAnimation = false; }); // Animation loop that does all the fun stuff function animate() { // Update x++; // Clear context1.clearRect(0, 0, canvasWidth, canvasHeight); // Draw context1.fillRect(x, 75, 50, 50); if (playAnimation) { // Run the animation loop again in 33 milliseconds setTimeout(animate, 33); }; }; // Start the animation loop animate(); });