原作者:Steven Riche
发布时间:2014年1月30日
原文链接:http://code.tutsplus.com/tutorials/javascript-animation-that-works-part-2-of-4--net-35237
翻译:子毅 ------- 将JavaScript进行到底
在上一篇文章中,我们介绍了在JavaScript中使用精灵,使得动画在所有浏览器中都能够工作。我们还介绍了怎样为一个div元素设置精灵背景,并且用一行JavaScript代码来改变背景的位置,使得图片看起来在动一样。
在本篇文章中,我们将使用这种技术来实现跑和跳的动作。为了制作动画,我们必须在有规律的时间间隔内,快速地改变背景位置。再一次看看我们所用的精灵
在我们的例子中,总共有十张图片:一张是J向右站着,三张J向右跑,一张J向右时跳起来(向左也是同样如此)。现在开始让它向右跑。为了使图片看起来在跑动,我们需要做两件事:改变精灵为另一张图片,将div元素向右移动。
向右跑的动画
我们肯定不想通过不停地点击不同的按钮来循环精灵,因此,我们需要创建一些函数来自动地完成这些事情。
我们想要执行函数来完成这些事情:
- 将div元素慢慢地向右移动
- 移动到下一帧动画
- 暂停一秒钟(保留“视觉暂留”幻觉)
- 再次循环函数
幸运的是,这里有一种简单的方式来循环执行函数。在JavaScript有一个原生的命令,叫做setTimeout,允许我们创造一段时间的延时,一段时间后我们将再次执行这个函数(在这个函数内部)
function run_right() {
//慢慢地向右移动……
//改变动画到下一帧……
//200毫秒后再次执行run_right函数
setTimeout(function() {run_right();}, 200);
}
现在我们就有了一个可以在一秒内调用自己执行五次的函数(这已经足够快,能够达到创建动画的目的)。但记住,各个浏览器并没有精准的计时器,你可以指定毫秒级的时间,但并不意味着你的脚本在那个时间执行!
下一个要解决的问题是,怎样让函数知道下一张精灵是什么?在这个例子中,我们需要在三张图片之间循环(来使得动画有四帧)。为了做到这一定,我们需要想函数传递一些信息来告诉它转化到哪一个slide。一旦函数执行,就检测在哪一个slide上,然后改变背景位置到正确的精灵。当再次执行函数时,将下一个slide作为参数传递给函数。
function run_right(slide) {
//慢慢地向右移动……
switch(slide) { //此开关语句检查不同可能性的幻灯片
case 1: //假如'slide'等于'1'
document.getElementById('j').style.backgroundPosition = '-40px 0px';
setTimeout(function() {run_right(2);}, 200);
break;
case 2: //假如'slide'等于'2'
document.getElementById('j').style.backgroundPosition = '-80px 0px';
setTimeout(function() {run_right(3);}, 200);
break;
case 3:
document.getElementById('j').style.backgroundPosition = '-120px 0px';
setTimeout(function() {run_right(4);}, 200);
break;
case 4:
document.getElementById('j').style.backgroundPosition = '-80px 0px';
setTimeout(function() {run_right(1);}, 200);
break;
}
}
现在我们执行一次函数,并保证给它传递开始slide
<input type="button" value="Run Right" οnclick="run_right(1);" />
相应地,为了将div元素也向右慢慢地移动,我们可以传递div的初始left属性值给函数,然后在函数每次执行的时候靠增加left值来向右移动。
function run_right(slide, left) {
//慢慢地向右移动……
left = left + 15;
document.getElementById('j').style.left = left + 'px';
switch(slide) { //此开关语句检查不同可能性的幻灯片
case 1: //假如'slide'等于'1'
document.getElementById('j').style.backgroundPosition = '-40px 0px';
setTimeout(function() {run_right(2, left);}, 200);
break;
case 2: //假如'slide'等于'2'
document.getElementById('j').style.backgroundPosition = '-80px 0px';
setTimeout(function() {run_right(3, left);}, 200);
break;
case 3:
document.getElementById('j').style.backgroundPosition = '-120px 0px';
setTimeout(function() {run_right(4, left);}, 200);
break;
case 4:
document.getElementById('j').style.backgroundPosition = '-80px 0px';
setTimeout(function() {run_right(1, left);}, 200);
break;
}
}
注意,在初始化函数时,要保证给函数传递div元素当前的左偏移量
<input type="button" value="Run Right" οnclick="run_right(1, document.getElementById('j').offsetLeft);" />
停止动画
现在我们已经有了一个函数,当调用它的时候,J会向右跑动。不幸的是,我们没有办法让它停下来。首先,当J跑到stage边缘的时候,函数会自动停止执行,为了做到这一点,每次函数执行的时候,我们需要检测一个if声明,判断J是否还有空间来跑动,假如有,函数正常执行,反之则停止函数的执行,并将精灵置于站立状态。
function run_right(slide, left) {
//假如将left增加15px,J的右边没有超过stage的边缘
if ((left + 15) < (document.getElementById('stage').offsetWidth - document.getElementById('j').offsetWidth)) {
//还有空间,函数正常执行
} else {//假如J超过stage右边,函数停止执行,将精灵置于站立状态
document.getElementById('j').style.backgroundPosition = '0px 0px';
}
}
最后,当需要的时候,我们想要以一种方式停止函数的执行。可以将setTimeout()赋值给一个变量,需要停止时,将这个变量传递给clearTimeout()。为了做到这一点,需要在函数外面声明一个变量,之后就可以应用它。现在,将这个变量声明为一个全局变量。但是这是一种糟糕的编程实践,但是我们会在下一篇文章中纠正它。现在我们的函数是这个样子。
var timer;
function run_right(slide, left){
if ((left + 15) < (document.getElementById('stage').offsetWidth - document.getElementById('j').offsetWidth)){
left = left + 15; // 将它的left属性值增加15px
document.getElementById('j').style.left = left+"px";
switch (slide){ //此开关语句检查不同可能性的幻灯片
case 1: //假如'slide'等于'1'...
document.getElementById('j').style.backgroundPosition = "-40px 0px";
setTimeout(function(){run_right(2, left);}, 200);
break;
case 2: //假如'slide'等于'2'...
document.getElementById('j').style.backgroundPosition = "-80px 0px";
setTimeout(function(){run_right(3, left);}, 200);
break;
case 3:
document.getElementById('j').style.backgroundPosition = "-120px 0px";
setTimeout(function(){run_right(4, left);}, 200);
break;
case 4:
document.getElementById('j').style.backgroundPosition = "-80px 0px";
setTimeout(function(){run_right(1, left);}, 200);
break;
}
} else {
document.getElementById('j').style.backgroundPosition = "0px 0px";
}
}
我们可以创建另一个函数来停止跑动计时器,并且将精灵置于站立状态
function stop_running() {
document.getElementById('j').backgroundPosition = '0px 0px';
clearTimeout(timer);
}
向左跑的动画
现在借用run_right()函数的代码,只需要做一点点修改,我们就可以创建另一个向左跑的函数run_left()
function run_left(stage, left){
if ((left - 15) > 0){
left = left - 15;
document.getElementById('j').style.left = left+"px";
switch (stage){
case 1:
document.getElementById('j').style.backgroundPosition = "-40px -50px";
timer = setTimeout(function(){run_left(2, left);}, 200);
break;
case 2:
document.getElementById('j').style.backgroundPosition = "-80px -50px";
timer = setTimeout(function(){run_left(3, left);}, 200);
break;
case 3:
document.getElementById('j').style.backgroundPosition = "-120px -50px";
timer = setTimeout(function(){run_left(4, left);}, 200);
break;
case 4:
document.getElementById('j').style.backgroundPosition = "-80px -50px";
timer = setTimeout(function(){run_left(1, left);}, 200);
break;
}
} else {
document.getElementById('j').style.backgroundPosition = "0px -50px";
}
}
跳跃动画
最后,我们需要创建一个跳跃函数。我们需要向这个函数传递两个参数,一个将跟踪当前div元素是否在向上或向下移动,另一个将跟踪div元素的当前top属性。通过这两个参数,我们将决定div下一步该怎样移动,并且知道移动多少(为了模拟重力加速度,div跳跃离弧线顶部更近的时候,我们将移动div较短的距离)
function jump (up, top) {
//我们改变J为跳跃精灵
document.getElementById('j').style.backgroundPosition = '-160px 0px';
//这里,我们需要决定精灵向下还是向上
if (up && (document.getElementById('j').offsetTop > 20)) {
//假如它正在向上移动,并且它距离stage顶部的距离大于20px
top = top - (top * .1); //这给了我们的跳跃一个轻微的弧线,而不是像跑一样一直保持一个速度
document.getElementById('j').style.top = top + 'px'; //改变位置
timer = setTimeout(function() {jump(up, top);}, 60); //再次执行函数
} else if (up) {
//假如它正在向上移动,但是它已经接近stage的顶部,并且需要向下
up = false; //改变up变量,因此下一次循环时它就会向下移动
timer = setTimeout(function() { jump(up, top); }, 60);
} else if (!up && (document.getElementById('j').offsetTop < 115)) {
//假如它在向下移动,但是离地面不止5px,它将继续向下移动
top = top + (top * .1);
document.getElementById('j').style.top = top + 'px';
timer = setTimeout(function() {jump(up, top);}, 60);
} else {
//假如它在向下移动,并且离地面在5px内
document.getElementById('j').style.top = '120px'; //将它放置在地面上
document.getElementById('j').style.backgroundPosition = '0px 0px'; //置为站立的精灵
//当它在这个点静止站立时我们将不在循环执行函数
}
}
现在分别绑定四个函数到四个按钮上,我们就得到了跑跳动画的原型!假如你喜欢的话,你可以在 这张页面查看完整的代码,并且有完整的注释,这里可以下载我所用的 精灵表
总结
尽管现在我们有了可工作的动画原型,但是你可能注意到这里有一些bug。当你点击多个按钮时,脚本会尝试执行多个动画,或者,当精灵向下跳跃的时候,你再次点击跳跃按钮,J将会一直向下,永不停止。同时,正如我之前提到的,我们的脚本中有全局变量,这意味着,可能很难将这些代码添加到现有页面中而不和其他JavaScript脚本引起冲突(这也是为什么我没有尝试在本博客页面运行代码的原因)。在下一篇文章中,我们将清除所有上述bug,并且谈论封装的概念,以及在现实世界中,为了编写高质量的代码,封装为什么是如此的重要