在当前的web应用中,我们可以看到Javascript动画效果得到了越来越多的应用。由于AJAX技术的流行,通过简单的动画效果提示用户当前正在执行的操作变得必要,例如最常见的效果就是颜色的渐变和元素大小的调整。
当前许多主流的Javascript类库或框架中都包含了一些基本动画效果的接口,让我们使用这些效果变得更加容易,本文并不是针对这些接口的使用教程,而是尝试探讨一下使用Javascript实现动画效果的基本原理和结构,希望能对需要自己实现一些自定义程度比较高的动画效果有一些帮助。
基于Javascript的动画其背后的基本原理是非常简单的。我们需要做的只是通过定义好的一组运动规则来操作DOM元素(例如>img<,<div>)在页面的区域上移动或是改变其相关的属性。
动画的基本原理是连续播放一组静态的图片从而产生动态的效果,其最基本的元素在于帧率。在其它一些语言里,例如C#或是Visual Basic,我们可以通过让相应进程sleep的方式来达到在一组动画的循环中控制帧率的效果。因此,实现动画效果的一组基本结构是执行如下的一组循环。
{
doSomething () ;
delay ( sometime ) ;
doOther () ;
}
但是在Javascript中我们并没有现成的函数接口来实现delay的效果,所以我们必须得借助于Javascript中window.setInterval来实现动画效果。在我们实现一个简单的动画效果的模式中,有这样几个原则是应该注意的:
1.避免使用全局变量,因为我们所提供的动画函数应该是可以同时应用于多个元素的。
2.将元素对象作为参数传递,而不是传递元素的ID。
3.注意线程安全的问题。我们通过setInterval和clearInterval的方式来实现当动画结束的时候,这个时钟将自动清除。并且需要保证同一类型的动画不会同时在多个实例上运行。
假设以一个简单的改变元素width属性的动画效果为示例,我们实现的函数应该是下面这个样子的:
{
if ( obj . widthChangeAnimate ) window . clearInterval ( obj . widthChangeAnimate ) ;
var cFrame = 0 ; // current frame
obj . widthChangeAnimate = window . setInterval (
function (){
obj . currentWidth = easeInOut ( startW , endW , frames , cFrame , power ) ;
obj . style . width = obj . currentWidth + " px " ;
cFrame ++;
if ( cFrame > frames ) window . clearInterval ( obj . widthChangeAnimate ) ;
}
, delay
) ;
}
在这个函数的接口当中,前三个参数的含义很好理解,第三个参数frames代表了这个动画效果将被划分为几帧来执行,第四个参数delay表示了两帧之间的时间间隔,通过这两个参数我们便可以有效的控制动画的帧率了。
obj的currentWidth是对元素本身的一个扩展,这是为了让元素能有记忆当前的状态。并且,当我们需要重复触发某个动画效果时,能够快速的恢复到元素的初始状态。例如我们对某个元素调用doWidthChangeAnimate的时候应该是这样的:
elem . onmouseout = doWidthRecover ;
function doWidthChange ()
{
if ( ! this . currentWidth ) this . currentWidth = 100 ;
doWidthChangeAnimate ( this , this . currentWidth , 200 , 24 , 10 , 0.5 ) ;
}
function doWidthRecover ()
{
if ( ! this . currentWidth ) return ;
doWidthChangeAnimate ( this , this . currentWidth , 150 , 24 , 10 , 0.5 ) ;
}
注意到上面的示例中doWidthChangeAnimate函数中调用了一个easeInOut函数来获取当前应该将元素的宽度设置为多少,这是因为在通常状况下,尽管我们可以按帧率计算出没一步应该增加的宽度平均值,但使用这个渐变平均值不一定能产生比较好的动画的效果,从动画理论的角度来说,其变化不应该是匀速的。所以我们通过一个函数来计算每一帧中所需要产生的变化,你可以自己选择合适的方式计算这个这个数值,一个可以参考的方式如下:
{
var delta = max - min ;
var p = min + Math . pow (( 1 / frames)*cFrame, power)*delta;
return Math.ceil(p);
}
实际应用中,例如大众点评网的单张图片页面里右上角的图片浏览模块就使用了本文所述的技术。利用Javascript实现这样的动画效果其实方式很多,本文只是提供了其中一种可以参考的模式。最后让我们看一下jQuery中动画效果实现的基本模式,jQuery中所有动画的基础其实都源于jQuery.fx.prototype中的方法custom。其源代码片段如下:
......
custom : function ( from , to , unit ){
this . startTime = now () ;
this . start = from ;
this . end = to ;
this . unit = unit || this . unit || " px " ;
this . now = this . start ;
this . pos = this . state = 0 ;
this . update () ;
var self = this ;
function t ( gotoEnd ){
return self . step ( gotoEnd ) ;
}
t . elem = this . elem ;
jQuery . timers . push ( t ) ;
if ( jQuery . timerId == null ) {
jQuery . timerId = setInterval ( function (){
var timers = jQuery . timers ;
for ( var i = 0 ; i < timers . length ; i ++ )
if ( ! timers [ i ]() )
timers . splice ( i --, 1 ) ;
if ( ! timers . length ) {
clearInterval ( jQuery . timerId ) ;
jQuery . timerId = null ;
}
} , 13 ) ;
} ,
......
} );
从这段代码可以看到,其实jQuery中动画的基础模式和本文中所提到的其基本思路是一致的,不过jQuery中使用了一个timers的数组来管理诸多的动画时钟,并且所有接口封装的更加完善和通用,耦合性也更低,其它一些关键函数例如update, step都在jQuery.fx.prototype的扩展中。而easing的函数在jQuery.extend中,jQuery提供了两种渐变效果,分别为linear和swing,其计算的公式的代码片段如下:
......
easing : {
linear : function ( p , n , firstNum , diff ) {
return firstNum + diff * p ;
} ,
swing : function ( p , n , firstNum , diff ) {
return (( - Math . cos ( p * Math . PI ) / 2) + 0.5) * diff + firstNum;
}
},
......
});
有兴趣的朋友可以继续深入研究一下其源代码,本文中就不再赘述了。