javascript动画、运动算法详细解释与分析 (一、Tween 运动算法学习笔记)
现在网上有很多javascript特效,这些特效最大的特点就是“会动”,也就是动画效果。实际上近年来使用javascript在网页中实现动画已经成为一种趋势,而前几年在网页动画领域相当风骚的Flash今年来也稍显低调。毕竟作为富客户端程序,Flash对于目前让人纠结的带宽来说还是稍显笨重了点儿,而且Flash必须要有插件支持。但javascript这种客户端脚本语言却可以只占用相当少的空间而且不需要繁琐的插件支持和下载就能很好的解决一些简单的动画问题,例如:运动、变形等等。
现在有很多优秀的类库已经做的很好了,类似于JQuery、ExtJs、prototype等,还有一些小型的animation、easing类库都实现的很好,不过细究之下,发现网上相关的详解资料很少。包括Tween算法,也是只有例子没有详细的解释,当然,不乏高手,只不过可能高手很少愿意在这上面花时间做解释吧。
下面我将尽本人所能来详细的解释相关的运动算法和在javascript中的应用实例,并尽可能多的提供相关资料供大家学习。
一、运动算法涉及到一些高中物理和数学知识
我是文科出身的,高中数理化相当烂。我最辉煌的一次数学成绩是9分,物理14分,有木有童鞋比我的还低?木有吧?恩,没人回答我就先当没有了...那么,我相信下面的一些知识对你来说应该相当easy。
运动:根据我们这里的需求,我们简单的把运动定义为随着时间的改变,物体的位置不断改变的现象。这里有三个要素:时间、起始位置、终点位置。而且位置和时间之间存在一对一的对应关系。
那么在javascript中,时间的问题我们可以用setInterval或者setTimeOut解决,位置的问题我们可以通过DOM对象的style属性控制。
设p(position)为位置,t(time)为时间,那么有
1: p = ƒ(t)
转换为javascript函数就是:
1: p = function(t){2: var v = 2;//速度3: return t*v;}我们知道速度 v(velocity) = 距离/时间,那么我们设距离为c(change positioin),经过时间为d(druation),那么
p = t * ( c / d )
我们可以通过上面这个公式得到物体在(c/d)的速度下移动时间t所在的位置p,但请注意,这里其实有个前提:物体的初始位置为0。
那么如果我们设物体的当前位置为b(begining positoin),那么
p = t * ( c / d ) + b
将这个公式代入javascript中,得(终于有高中算方程题的感觉了)
1: p = funciton( t, b, c, d ){
2: return t * ( c / d ) + b;3: }
格式化,得
1: Math.prototype.linearTween = function( t, b, c, d){2: return t*c/d + b;3: }
这样,我们就得到了最简单的直线运动函数。
为了更加形象的描述问题,我们引入坐标系,那么直线运动在这里就可以表示为(图1),这就是我们高中数学中最简单的线性表达式:y=x;此时斜率为1。
二、一个简单的例子
那么下面我们将这个公式引入到javascript中,做一个简单的例子来看一下实际的效果。
1: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">2: <html xmlns="http://www.w3.org/1999/xhtml">3: <head>4: <title>TweenTest</title>5: <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />6: <meta name="generator" content="NTCSoft" />7: <meta name="author" content="MrZhou" />8: <meta name="keywords" content="" />9: <meta name="description" content="" />10: <style type="text/css">11: #moveLinear{
12: width:50px;
13: height:50px;
14: background:#ccc;
15: border: 1px solid;
16: }
17: </style>18:
19: </head>20:
21: <body>22: <div id="moveLinear"></div>23: </body>24: <script type="text/javascript">25:
26: //将$设置为获取元素对象的函数,留待以后使用。27: $ = function( id ){ return typeof id == "string"?document.getElementById(id):id }28:
29: /* des:tween算法。30: t: 动画已经执行的时间(实际上时执行多少次/帧数)31: b: 起始位置32: c: 终止位置33: d: 从起始位置到终止位置的经过时间(实际上时执行多少次/帧数)*/34: tween = {
35: linear : function( t, b, c, d){36: return t*c/d + b;37: }
38: }
39: /* des: 控制元素移动的动画对象40: moveType : 元素移动的方式,默认为linear 。41: 参数:42: mvTp = string 移动方式43: startMove : 移动元素的函数44: 参数:45: mvObj = string 被移动的元素的id46: mvTp = string 移动方式*/47: move={
48: //元素移动的方式:默认为linear49: moveType : function(mvTp){return mvTp && typeof(mvTp)== ”string” && tween[mvTp] ? mvTp: "linear" },50:
51: //移动元素的函数52: //mvObj:string 被移动的元素的id53: //mvTp:string 移动类型54: startMove : function( mvObj,mvTp,t,b,c,d ){55:
56: //判断传入参数是否正确,如果t存在那么还是t,否则为t设置默认值,其他的以此类推57: t ? t : t=0; b ? b : b = 0; c ? c : c =300; d ? d : d = 50;
58: //判断被移动的元素是否存在相对或者绝对的定位属性,1 的作用仅仅是为了完成语法格式(若没有该属性,则无法移动元素)59: $(mvObj).style.position =="relative" || $(mvObj).style.position =="absolute"60: ? 1 : $(mvObj).style.position = "relative";61:
62: //每隔30毫秒重复执行改变元素位置的函数63: mvTimer = setInterval(function(){64: //判断动画已经执行的时间(次数/帧数)是否小于总时间,是的话继续执行改变位置的函数,否则的话,清理该interval。65: //?和:之间的函数是个匿名函数,在匿名函数的后面加上()直接调用该函数,简便写法。66: //tween[move.moveType(mvTp)]() 先通过对象数组的属性obj["x"]方式访问tween的某个属性对象,然后加上()执行该函数对象。67: t <= d
68: ? function(){ $(mvObj).style.left = parseInt(tween[move.moveType(mvTp)](t,b,c,d))+"px"; t++;}()69: : clearInterval(mvTimer);
70: } ,30 )
71: }
72: }
73:
74: move.startMove("moveLinear");75: </script>76: </html>通过上面你的动画,我们发现灰色方框可以很顺利的从左边移动到右边,这好像已经达到了我们的要求,但如果我们再上面这个例子简单的将一个方框从左边的0px以6px/30ms的速度移动到右边300px的位置(这里为什么是以6px/30ms的速度等下解释),现在我们来看一下动画消耗时间,按照我们的理论,每30ms移动6px,那么300px的动画时间应该是30ms * 50 = 1500ms,但如果童鞋们再细心的测试一下,就会发现,动画总时间并不像我们想象的那样准确。
为此,我们在程序中间加两个时间戳,来计算动画总共的时常:
1: ……
2: $(mvObj).style.position =="relative" || $(mvObj).style.position =="absolute"3: ? 1 : $(mvObj).style.position = "relative";4: //开始时间戳5: strT = new Date();6: //每隔30毫秒重复执行改变元素位置的函数7: mvTimer = setInterval(function(){8: ……
1: ……
2: ? function(){ $(mvObj).style.left = parseInt(tween[move.moveType(mvTp)](t,b,c,d))+"px"; t++;}()3: //结束时间戳,并弹出动画总时长4: : function(){ clearInterval(mvTimer); alert(new Date() - strT); }()5: } ,30 )
6: ……
得出如下结果:
而且,如果你多测试几次发现每次的结果可能都不尽相同,这对我们精确控制动画执行时间绝对是个噩耗.深究了一下,发现时间误差的原因还真是比较让人纠结。
那么在我们进入下一阶段之前,就先来看一下这个问题,省的到了后面问题复杂度增加的时候再解决就相对比较麻烦了。