jQuery源码之动画详解二

一。前言

上面篇讲了动画的基础功能模块queue队列,这篇继续讲动画具体是怎么实现的,接着上一篇

//	动画测试
	$("#aaron").animate({height:"300px",width:"440px"},1000,"swing",function(){
		console.log("Animation Show.")
	})/*.animate({height:"30px",width:"110px"},1000,"linear",function(){
		console.log("Animation Show.")
	})*/;


在上一篇最后,fn.call( elem, next, hooks ); //在这里调用 doAnimation,这里是执行动画的入口

animate: function( prop, speed, easing, callback ) {
		
		//	Object {height: "hide", paddingTop: "hide", marginTop: "hide", paddingRight: "hide", marginRight: "hide"…} "slow" fn undefined
		var empty = jQuery.isEmptyObject( prop ),	//	false
		//	组装参数
			optall = jQuery.speed( speed, easing, callback );	//	Object {complete: function, duration: 600, easing: undefined, queue: "fx", old: function}
		
		//	每次动画函数都是把这个函数推入到queue中,只不过是prop,optall参数不同,产生不同的动画效果,其实还是一个一个动画执行,主不过可以链式写法
		var	doAnimation = function() {
				// arguments [function, Object]  上下文为elem
				//fn.call(next, hooks);
				// Operate on a copy of prop so per-property easing won't be lost
				//	div#aaron Object object
				//	这里执行动画的函数 elem,prop,optall				
				var anim = Animation( this, jQuery.extend( {}, prop ), optall );
				
				// Empty animations, or finishing resolves immediately
				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 );	//	this.queue('fx',doAnimation)  jQuery.fn.queue
	},
this.queue( optall.queue, doAnimation );把动画函数插入队列中,然后dequeue出列执行fn.call( elem, next, hooks );
elem就是DOM元素,也是动画函数的上下文context,next就是迭代作用的函数,其中就有dequeue函数,取出下个动画执行,如果有N个动画,就会迭代N次,hooks就是我们前面讲过的每次动画完进行清理的钩子函数
(1)<span style="font-family: Arial, Helvetica, sans-serif;">var anim = Animation( this, jQuery.extend( {}, prop ), optall );</span>
prop参数就是上面的 {height:"300px",width:"440px"},optall就是 jQuery.speed( speed, easing, callback );组装后的动画配置,动画时间,动画速度,还有我们的自定义回调函数

//	这个函数才是正在执行动画的函数
//	div#aaron  css属性  动画参数
function Animation( elem, properties, options ) {
	var result,
		stopped,
		index = 0,
		length = animationPrefilters.length,	//	1	[ defaultPrefilter ]
		//	done fail向2个队列中添加函数,并且返回this
		//	luolaifa	第四个添加的函数,向成功和失败函数队列添加回调函数 delete tick.elem;
		deferred = jQuery.Deferred().always( function() {
			// don't match elem in the :animated selector
			delete tick.elem;
		});
	
	//	每隔13ms执行这个函数
	var 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;	//	总共有11个css属性变化,length = 11
			
			//	每隔13ms都循环调用各个属性的run函数,改变当前的百分比,并设置新的css属性值
			for ( ; index < length ; index++ ) {
				
				animation.tweens[ index ].run( percent );
			}
			
			//	每隔13ms,都会执行progress回调函数队列
			deferred.notifyWith( elem, [ animation, percent, remaining ]);
			
			if ( percent < 1 && length ) {
			//	动画完成之前,返回剩余的时间
				return remaining;
			} else {
				
			//  当动画完成之后,属性变化完成后,激发resolve回调函数队列	
			// luolaifa  总共压入了8个函数
			//1.简单的复制   2.当动画完成的时候,清空回调函数队列  3.给当前回调函数队列上锁
			//4.	第四个添加的函数,向成功和失败函数队列添加回调函数 执行  delete tick.elem;
			//5. 向成功和失败回调队列第五个添加的函数,执行 overflow,overflowX,overflowY = ''
			//6. 向成功和失败回调队列第六个添加的函数,在本次动画结束后调用 jQuery( elem ).hide();	 showHide() 设置缓冲数据olddisplay
			/*
			//7. 向成功和失败回调队列第七个添加的函数,在本次动画结束后调用
					anim.done(function() {		
						var prop;
						jQuery._removeData( elem, "fxshow" );	//	清空fxshow的缓存数据
						for ( prop in orig ) {
							
							jQuery.style( elem, prop, orig[ prop ] );	//	设置css
						}
					});
			*/
			/*
			//8.第八个被推入的函数	调用dequeue出列下一个动画
				opt.complete = function() {
					if ( jQuery.isFunction( opt.old ) ) {
						opt.old.call( this );
					}
					
					if ( opt.queue ) {
						
						jQuery.dequeue( this, opt.queue );
					}
				};
			*/
				deferred.resolveWith( elem, [ animation ] );
				return false;
			}
		};
		
		//	合并对象
		var 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 ) {
				
				//				 prop(属性名)  0 
				//			jQuery.Tween(div#aaron,animation.opts,prop,0,swing)
				var tween = jQuery.Tween( elem, animation.opts, prop, end,
						animation.opts.specialEasing[ prop ] || animation.opts.easing );
				//	将实例化的tween推入animation.tweens中
				
				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 ) {
					//	当动画结束的时候,激发resolve(成功)回调函数队列
					deferred.resolveWith( elem, [ animation, gotoEnd ] );
				} else {
					//	当动画没有正常结束的时候,激发reject(失败)回调函数队列
					deferred.rejectWith( elem, [ animation, gotoEnd ] );
				}
				return this;
			}
		}),
	//{height: "hide", paddingTop: "hide", marginTop: "hide", paddingRight: "hide", marginRight: "hide"…}
	props = animation.props;
	
	//	css属性钩子
	//propFilter(object,{})
	//	过滤特殊属性的变化速度
	
	propFilter( props, animation.opts.specialEasing );
	
	for ( ; index < length ; index++ ) {
		//											Object     div#aaron  object  object
		//	动画之前,在对特殊属性进行过滤/主要是为每个属性创建动画参数
		result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
		
		if ( result ) {
			return result;
		}
	}
	
	//	执行了这行代码,但是好像没怎么影响
	jQuery.map( props, createTween, animation );
	//	false
	if ( jQuery.isFunction( animation.opts.start ) ) {
		animation.opts.start.call( elem, animation );
	}
	
	
	//	这里用的是定时器,定时执行动画,之前为每个属性都定义了start,end,swing值,可以根据这个来变化直到结束	
	jQuery.fx.timer(
		//合并tick,然后作为参数传给jQuery.fx.timer
		jQuery.extend( tick, {
			elem: elem,
			anim: animation,
			queue: animation.opts.queue
		})
	);
	
	// attach callbacks from options
	//	luolaifa 第八个被推入的函数  
	return animation.progress( animation.opts.progress )
		.done( animation.opts.done, animation.opts.complete )
		.fail( animation.opts.fail )
		.always( animation.opts.always );
}
(1)deferred = jQuery.Deferred().always( function() {
// don't match elem in the :animated selector
delete tick.elem;
});
这里是生成一个延迟deferred对象,然后调用always将 函数参数添加到resolve和resolve回调函数队列当中 

delete tick.elem;这句主要是在执行到后面清除内存的作用,防止内存泄露

Deferred: function( func ) {		
		var tuples = [
				// action, add listener, listener list, final state
				[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
				[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
				[ "notify", "progress", jQuery.Callbacks("memory") ]
			],
			state = "pending",
			promise = {
				state: function() {
					return state;
				},
				always: function() {
					deferred.done( arguments ).fail( arguments );					
					return this;
				},
				then: function( /* fnDone, fnFail, fnProgress */ ) {
					var fns = arguments;
					return jQuery.Deferred(function( newDefer ) {
						jQuery.each( tuples, function( i, tuple ) {
							var action = tuple[ 0 ],
								fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
							// deferred[ done | fail | progress ] for forwarding actions to newDefer
							deferred[ tuple[1] ](function() {
								var returned = fn && fn.apply( this, arguments );
								if ( returned && jQuery.isFunction( returned.promise ) ) {
									returned.promise()
										.done( newDefer.resolve )
										.fail( newDefer.reject )
										.progress( newDefer.notify );
								} else {
									newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
								}
							});
						});
						fns = null;
					}).promise();
				},
				// Get a promise for this deferred
				// If obj is provided, the promise aspect is added to the object
				promise: function( obj ) {					
					return obj != null ? jQuery.extend( obj, promise ) : promise;
				}
			},
		deferred = {};

		// Keep pipe for back-compat
		promise.pipe = promise.then;

		// Add list-specific methods
		jQuery.each( tuples, function( i, tuple ) {
			
			var list = tuple[ 2 ],
				stateString = tuple[ 3 ];
			
			// promise[ done | fail | progress ] = list.add
			promise[ tuple[1] ] = list.add;

			// Handle state
			if ( stateString ) {
			//	luolaifa 第一个压入的函数 第二个压入的函数 第三个压入的函数
			//	当动画完成的时候,就会执行list回调里面的函数
			//  1.简单的复制   2.当动画完成的时候,清空或禁用resolve/reject回调函数队列  3.给progress的回调函数队列上锁
				list.add(function() {		
					// state = [ resolved | rejected ]
					state = stateString;					
				// [ reject_list | resolve_list ].disable; progress_list.lock
				}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
			}
			
			// deferred[ resolve | reject | notify ]
			// deferred[ resolveWith | rejectWith | notifyWith ]
			
			deferred[ tuple[0] ] = function() {
				deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
				return this;
			};
			deferred[ tuple[0] + "With" ] = list.fireWith;
		});
		
		// Make the deferred a promise
		//	合并promise和内部的deferred对象
		
		promise.promise( deferred );
		
		// Call given func if any
		if ( func ) {
			func.call( deferred, deferred );
		}
		
		// All done!
		//	最后返回合并后的deferred
		return deferred;
	},
(1)tuples是一个JS二维数组,第一列是他们的名词,resolve/成功,reject/失败,progress/进行,以此类推,第二列是他们的回调函数的接口,比如如果一个动画执行完后,要执行一系列的回调函数,所以每种结果都有自己的回调函数队列,而第二列就是他们新增的接口add(Callbacks.add方法),第三个就是实例化的Callbacks对象,

jQuery.Callbacks(once memory),once表示只能激发一次,就算以后在add,然后fire都不会执行函数队列,而memory是当第一次add之后,激活memory状态,以后add函数的同时执行新增的函数,而jQuery在这里把这两种结合在一起,就可以每次新增的函数只会执行一次,只要激发玩后就会清空队列,但是下次可以继续add,继续激发,这就结合了once和memory达到这种效果

第四个参数就是他们动画介绍后的状态,如果成功就为resolved,失败为rejected,而进行中就没有状态 。

(2)promise按照我的理解应该是对延迟对象的一个约定,等动画执行完后执行回调队列中的函数

(3).

// Add list-specific methods
		jQuery.each( tuples, function( i, tuple ) {
			
			var list = tuple[ 2 ],
				stateString = tuple[ 3 ];
			
			// promise[ done | fail | progress ] = list.add
			promise[ tuple[1] ] = list.add;

			// Handle state
			if ( stateString ) {
			//	luolaifa 第一个压入的函数 第二个压入的函数 第三个压入的函数
			//	当动画完成的时候,就会执行list回调里面的函数
			//  1.简单的复制   2.当动画完成的时候,清空或禁用resolve/reject回调函数队列  3.给progress的回调函数队列上锁
				list.add(function() {		
					// state = [ resolved | rejected ]
					state = stateString;					
				// [ reject_list | resolve_list ].disable; progress_list.lock
				}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
			}
			
			// deferred[ resolve | reject | notify ]
			// deferred[ resolveWith | rejectWith | notifyWith ]
			
			deferred[ tuple[0] ] = function() {
				deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
				return this;
			};
			deferred[ tuple[0] + "With" ] = list.fireWith;
		});
主要是遍历tuples 来给deferred对象赋予一些初始化的方法,

promise[ tuple[1] ] = list.add;这就是我们开始说了每个状态都有一个新增回调函数的接口, promise['done'] = list.add promise['fail'] = list.add promise['progress'] = list.add

注意这里的list是它们自己对应的Callbacks对象,   list = tuple[2]

if ( stateString ) {
			//	luolaifa 第一个压入的函数 第二个压入的函数 第三个压入的函数
			//	当动画完成的时候,就会执行list回调里面的函数
			//  1.简单的复制   2.当动画完成的时候,清空或禁用resolve/reject回调函数队列  3.给progress的回调函数队列上锁
				list.add(function() {		
					// state = [ resolved | rejected ]
					state = stateString;					
				// [ reject_list | resolve_list ].disable; progress_list.lock
				}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
			}
这里的stateString只有成功和失败为真,所以只有这两种状态的回调函数列表添加了这3个函数,

 1.简单的复制   2.当动画完成的时候,disable禁用resolve/reject回调函数队列  3.给progress的回调函数队列上锁
<pre name="code" class="javascript">之后就是各自copy Callbacks对象的fireWith方法
</pre>
(4)promise.promise( deferred );  将promise对象 和deferred对象合并成一个对象,方法和属性都合并,都可以调用,最后返回这个合并后的对象。每次jQuert.Deferred都返回合并后的对象。

deferred = jQuery.Deferred().always( function() {
			// don't match elem in the :animated selector
			delete tick.elem;
		});
(1)always方法是在promise方法,但是合并后,deferred对象也可以调用,这里的作用就是往成功和失败回调函数队列新增函数 ,done和fail就是list.add函数

always: function() {

					deferred.done( arguments ).fail( arguments );					
					return this;
				},
之后就是tick函数,这个函数就是每次动画时间间隔13ms执行的函数,每13ms执行一次,直到动画完成,稍后我们具体讲解,我们先往下讲

//	合并对象
		var 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 ) {
				
				//				 prop(属性名)  0 
				//			jQuery.Tween(div#aaron,animation.opts,prop,0,swing)
				var tween = jQuery.Tween( elem, animation.opts, prop, end,
						animation.opts.specialEasing[ prop ] || animation.opts.easing );
				//	将实例化的tween推入animation.tweens中
				
				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 ) {
					//	当动画结束的时候,激发resolve(成功)回调函数队列
					deferred.resolveWith( elem, [ animation, gotoEnd ] );
				} else {
					//	当动画没有正常结束的时候,激发reject(失败)回调函数队列
					deferred.rejectWith( elem, [ animation, gotoEnd ] );
				}
				return this;
			}
		}),
这段代码主要是将这些方法和属性,合并到deferred对象中,方便后面调用

重点讲几个属性和方法,其他的自己看看

1.startTime : fxNow || createFxNow(),createFxNow返回当前时间戳,就是动画开始的时间

2.stop属性主要是当动画完成后,激发成功或失败函数队列进行善后工作。

3.createTween函数是为每个要变化的属性实例化一个tween对象,这里我们只改变了height,width2个属性,所以就会实例化两个实例对象,但是实例化后存哪呢,上面还有个tweens空数组,就是用来存放实例化后的tween对象

下面就是关于Tween类的具体代码
<pre name="code" class="html">function Tween( elem, options, prop, end, easing ) {
	//	div#aaron,animation.opt ,prop,0,swing
	
	return new Tween.prototype.init( elem, options, prop, end, easing );
}
jQuery.Tween = Tween;

Tween.prototype = {
	constructor: Tween,
	//	初始化一些动画的参数
	init: function( elem, options, prop, end, easing, unit ) {
		this.elem = elem;
		this.prop = prop;
		this.easing = easing || "swing";	//	线性变化还是振幅变化
		this.options = options;
		//	得到动画开始之前,元素样式属性的起始值  从start值变化到end的值
		this.start = this.now = this.cur();
		this.end = end;		//	0
		this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );		
	},
	//	得到动画开始之前,元素样式属性的起始值
	cur: function() {
		var hooks = Tween.propHooks[ this.prop ];
		
		return hooks && hooks.get ?
			hooks.get( this ) :	
			Tween.propHooks._default.get( this );	//	获取元素样式属性的起始值
	},
	run: function( percent ) {
		
		var eased,
			hooks = Tween.propHooks[ this.prop ];
		
		//	根据percent算出下个13ms的百分百eased
		if ( this.options.duration ) {
			this.pos = eased = jQuery.easing[ this.easing ](
				percent, this.options.duration * percent, 0, 1, this.options.duration
			);
			
		} else {
			this.pos = eased = percent;
		}
		//	根据上面算出来的百分比  (结尾位置-起始位置)*已过时间百分比 + 加上起始的位置 = 已经走过的距离+加上起始的位置
		this.now = ( this.end - this.start ) * eased + this.start;
		
		if ( this.options.step ) {
			this.options.step.call( this.elem, this.now, this );
		}
		
		if ( hooks && hooks.set ) {
			
			hooks.set( this );
		} else {
			//	每过13ms,为每个属性设置新的值,才有动画的效果
			Tween.propHooks._default.set( this );
		}
		return this;
	}
};

Tween.prototype.init.prototype = Tween.prototype;

Tween.propHooks = {
	_default: {
		get: function( tween ) {
			var result;

			if ( tween.elem[ tween.prop ] != null &&
				(!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {
				return tween.elem[ tween.prop ];
			}

			// passing an empty string as a 3rd parameter to .css will automatically
			// attempt a parseFloat and fallback to a string if the parse fails
			// so, simple values such as "10px" are parsed to Float.
			// complex values such as "rotate(1rad)" are returned as is.
			
			result = jQuery.css( tween.elem, tween.prop, "" );			
			// Empty strings, null, undefined and "auto" are converted to 0.
			return !result || result === "auto" ? 0 : result;
		},
		set: function( tween ) {
			// use step hook for back compat - use cssHook if its there - use .style if its
			// available and use plain properties where available
			
			if ( jQuery.fx.step[ tween.prop ] ) {
				
				jQuery.fx.step[ tween.prop ]( tween );
			} else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {
				jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );	//	设置当前的css属性值
			} else {
				
				tween.elem[ tween.prop ] = tween.now;
			}
		}
	}
};

(1) return new Tween.prototype.init( elem, options, prop, end, easing );
 
 
<span style="font-family:Arial, Helvetica, sans-serif;">这里调用了原型中的init方法,初始化一个对象并返回,初始化中初始化了每个属性的动画参数,每个属性的起始值,介绍值,变化速率,等等</span>
<span style="font-family:Arial, Helvetica, sans-serif;">(2)this.cur()     获取元素属性的当前值
</span>
<span style="font-family:Arial, Helvetica, sans-serif;"></span><pre name="code" class="html">//	得到动画开始之前,元素样式属性的起始值
	cur: function() {
		var hooks = Tween.propHooks[ this.prop ];
		
		return hooks && hooks.get ?
			hooks.get( this ) :	
			Tween.propHooks._default.get( this );	//	获取元素样式属性的起始值
	},


 
 

animation.tweens.push( tween );这句代码就是将实例化的tween对象推入animation.tweens数组中。

//	css属性钩子
	//propFilter(object,{})
	//	过滤特殊属性的变化速度
	
	propFilter( props, animation.opts.specialEasing );
还是放在下篇讲吧!动画内容蛮多的。








猜你喜欢

转载自blog.csdn.net/luolaifa000/article/details/44571359