jQuery 1.9.1源码分析系列(十五)动画处理之缓动动画核心Tween :https://www.jb51.net/article/75821.htm
a.动画入口jQuery.fn.animate函数执行流程详解
--------------------------------------------------------------------------------
先根据参数调用jQuery.speed获取动画相关参数,得到一个类似如下的对象;并且生成动画执行函数doAnimation
optall = { complete: fnction(){...},//动画执行完成的回调 duration: 400,//动画执行时长 easing: "swing",//动画效果 queue: "fx",//动画队列 old: false/fnction(){...}, } var empty = jQuery.isEmptyObject( prop ), optall = jQuery.speed( speed, easing, callback ), doAnimation = function() { //在特征的副本上操作,保证每个特征效果不会被丢失 var anim = Animation( this, jQuery.extend( {}, prop ), optall ); doAnimation.finish = function() { anim.stop( true ); }; //空动画或完成需要立马解决 if ( empty || jQuery._data( this, "finish" ) ) { anim.stop( true ); } }; doAnimation.finish = doAnimation;
没有动画正在执行则马上执行动画,否则将动画压入动画队列等待执行
//没有动画在执行则马上执行动画,否则将动画压入动画队列等待执行 return empty || optall.queue === false ? this.each( doAnimation ) : this.queue( optall.queue, doAnimation );
可以看出,真正执行动画的地方是Animation( this, jQuery.extend( {}, prop ), optall )函数
b. jQuery内部函数Animation详解
--------------------------------------------------------------------------------
Animation ( elem, properties, options ). properties是要进行动画的css特征,options是动画相关选项{complete: function () {…},duration: 400,easing: undefined,old: false,queue: "fx"}。
首先,初始化一个延时对象,这个延时对象用来处理动画队列。
deferred = jQuery.Deferred().always( function() { // don't match elem in the :animated selector delete tick.elem; }),
然后,生成一个每一个时间点(相邻两个时间点的事件间隔默认为13毫秒)上都会执行的函数tick,这个tick函数会保存在jQuery.timers中,然后每次执行jQuery.fx.tick的时候会取出来执行。
tick = function() { if ( stopped ) { return false; } var currentTime = fxNow || createFxNow(), remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), // archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497) temp = remaining / animation.duration || 0, percent = 1 - temp, index = 0, length = animation.tweens.length; //执行动画效果 for ( ; index < length ; index++ ) { animation.tweens[ index ].run( percent ); } //生成进度报告 deferred.notifyWith( elem, [ animation, percent, remaining ]); if ( percent < 1 && length ) { return remaining; } else { //动画执行完毕,执行所有延时队列中的函数(包括清除动画相关的数据) deferred.resolveWith( elem, [ animation ] ); return false; } }
我们看到jQuery对动画进度的处理:
remaining = Math.max( 0, animation.startTime + animation.duration - currentTime )temp = remaining / animation.duration || 0,percent = 1 - temp,
进度百分比 = 1 - 剩余时间百分比。
平常我们是这么处理:假设时间13毫秒执行一次动画,当前是第n此执行,总的动画时长为T。那么
进度百分比 = (n*13)/T
实际上这种算法得到的时间n*13是不准确的,因为cpu不只是你一个程序在执行,时间片分给你的时候往往都比n*13大。而且是一个很不准确的值,导致动画感觉时快时慢,不连贯。而jQuery这种方式保证当前的事件点上动画执行结果的准确性,毕竟事件是最新计算结果。
第三,生成动画用的所有特征组成的对象animation(这个对象结构如源码所示),animation.props中保存的是用户传入的特征(动画最终目标)。
animation = deferred.promise({ elem: elem, props: jQuery.extend( {}, properties ), opts: jQuery.extend( true, { specialEasing: {} }, options ), originalProperties: properties, originalOptions: options, startTime: fxNow || createFxNow(), duration: options.duration, tweens: [], createTween: function( prop, end ) { var tween = jQuery.Tween( elem, animation.opts, prop, end, animation.opts.specialEasing[ prop ] || animation.opts.easing ); animation.tweens.push( tween ); return tween; }, stop: function( gotoEnd ) { var index = 0, // if we are going to the end, we want to run all the tweens // otherwise we skip this part length = gotoEnd ? animation.tweens.length : 0; if ( stopped ) { return this; } stopped = true; for ( ; index < length ; index++ ) { animation.tweens[ index ].run( 1 ); } // resolve when we played the last frame // otherwise, reject if ( gotoEnd ) { deferred.resolveWith( elem, [ animation, gotoEnd ] ); } else { deferred.rejectWith( elem, [ animation, gotoEnd ] ); } return this; } })