Effects源码

1.effects.js

创建animation类,对外提供生成定时器实际内容执行类tween的creatTween接口,以及调用tween.run执行定时函数内容的tick接口,定时机制通过$.timers管理和调用,回调函数采用deferred对象实现,queue队列函数在stop、finish方法和预处理中管理。

display、scroll等样式通过defaultRrefilter函数特别处理。

define([
	"./core",
	"./var/document",
	"./var/rcssNum",
	"./var/rnotwhite",
	"./css/var/cssExpand",
	"./css/var/isHiddenWithinTree",// 判断元素是否隐藏
	"./css/var/swap",
	"./css/adjustCSS",
	"./data/var/dataPriv",
	"./css/showHide",// show、hide方法的基层依赖,提供元素的显示隐藏方法

	"./core/init",
	"./queue",
	"./deferred",
	"./traversing",
	"./manipulation",
	"./css",
	"./effects/Tween"
],function(jQuery,document,rcssNum,rnotwhite,cssExpand,isHiddenWithinTree,swap,
	adjustCSS,dataPriv,showHide){

var
	fxNow, timerId,
	rfxtypes=/^(?:toggle|show|hide)$/,
	rrun=/queueHooks$/;

function raf(){
	if ( timerId ){
		window.requestAnimationFrame(raf);
		jQuery.fx.tick();
	}
}

// 获取当前时间
function createFxNow(){
	window.setTimeout(function(){
		fxNow=undefined;
	} );
	return ( fxNow=jQuery.now() );
}

// $ele.slideDown|slideUp|slideToggle|fadeIn|fadeOut|fadeToggle|toggle|hide|show方法传入的样式对象
function genFx(type,includeWidth){
	var which,
		i=0,
		attrs={height:type};

	includeWidth=includeWidth ? 1 : 0;
	for ( ; i<4 ; i+=2-includeWidth ){
		which=cssExpand[i];
		attrs["margin"+which]=attrs["padding"+which]=type;
	}

	if ( includeWidth ){
		attrs.opacity=attrs.width=type;
	}

	return attrs;
}

// 创建样式处理类tween,tween.run方法获取样式处理值
function createTween(value,prop,animation){
	var tween,
		collection=(Animation.tweeners[prop] || []).concat(Animation.tweeners["*"]),
		index=0,
		length=collection.length;
	for ( ; index<length; index++ ){
		if ( (tween=collection[index].call(animation,prop,value)) ){
			return tween;
		}
	}
}

// 默认的属性预处理函数,以animation方法执行时创建的延迟对象animation作为上下文
// elem为调用animation方法的元素,props对象形式的样式终值
// opts包含用户设置的回调函数,以及缓动效果等
function defaultPrefilter(elem,props,opts){
	var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display,
		isBox="width" in props || "height" in props,
		anim=this,
		orig={},
		style=elem.style,
		hidden=elem.nodeType && isHiddenWithinTree(elem),
		dataShow=dataPriv.get(elem,"fxshow");

	// $ele.animation({queue:true}) 撤销queue方法添加的回调函数在动画完成后的执行
	if ( !opts.queue ){
		// 取出queue方法创建的callback对象,其触发函数hooks.empty.fire添加到anim.always中
		// 动画执行完成或失败时,触发执行queue防范添加的回调函数
		hooks=jQuery._queueHooks(elem,"fx");
		if ( hooks.unqueued==null ){
			hooks.unqueued=0;
			oldfire=hooks.empty.fire;
			hooks.empty.fire=function(){
				if ( !hooks.unqueued ){
					oldfire();
				}
			};
		}
		hooks.unqueued++;

		anim.always(function(){
			anim.always(function(){// 延迟对象成功或失败状态均触发回调
				hooks.unqueued--;
				if ( !jQuery.queue(elem,"fx").length ){
					hooks.empty.fire();
				}
			} );
		});
	}

	for ( prop in props ){// props形为{height:"hide"}
		value=props[prop];
		if ( rfxtypes.test(value) ){// 含toggle、hide、show
			delete props[prop];
			toggle=toggle || value==="toggle";
			if ( value===( hidden ? "hide" : "show" ) ){

				if ( value==="show" && dataShow && dataShow[prop]!==undefined ){
					hidden=true;
				}else{
					continue;
				}
			}
			// 元素原始样式值,需要用户配置的样式值为show或hide或toggle,可以是width等值
			orig[prop]=dataShow && dataShow[prop] || jQuery.style(elem,prop);
		}
	}

	// show、hide方法,props为空对象,propTween为否
	propTween=!jQuery.isEmptyObject(props);
	if ( !propTween && jQuery.isEmptyObject(orig) ){
		return;
	}

	// 处理width、height样式时需要考虑overflow、display:inline-block样式
	// restoreDisplay获取页面元素默认的display或初始的display样式
	if ( isBox && elem.nodeType===1 ) {

		opts.overflow=[style.overflow, style.overflowX, style.overflowY];

		restoreDisplay=dataShow && dataShow.display;
		if ( restoreDisplay==null ){
			restoreDisplay=dataPriv.get(elem,"display");
		}
		display=jQuery.css(elem,"display");
		if ( display==="none" ){
			if ( restoreDisplay ){
				display=restoreDisplay;
			}else{
				// 先显示元素,获取元素默认的display样式值,再隐藏
				showHide([elem],true);
				// restoreDisplay存储元素的默认display值
				restoreDisplay=elem.style.display || restoreDisplay;
				display=jQuery.css(elem,"display");
				showHide([elem]);
			}
		}

		// 内联display:inline且float:none的元素动效执行过程中display:inline-block处理
		// 动效执行完成,回到原始值
		if ( display==="inline" || display==="inline-block" && restoreDisplay!=null ){
			if ( jQuery.css(elem,"float")==="none" ){
				if ( !propTween ){
					anim.done(function(){
						style.display=restoreDisplay;
					});
					if ( restoreDisplay==null ){
						display=style.display;
						restoreDisplay=display==="none" ? "" : display;
					}
				}
				style.display="inline-block";
			}
		}
	}

	if ( opts.overflow ){
		style.overflow="hidden";
		anim.always( function(){
			style.overflow=opts.overflow[0];
			style.overflowX=opts.overflow[1];
			style.overflowY=opts.overflow[2];
		});
	}

	// Implement show/hide animations
	propTween=false;
	for ( prop in orig ){

		// General show/hide setup for this element animation
		if ( !propTween ){
			if ( dataShow ){
				if ( "hidden" in dataShow ){
					hidden=dataShow.hidden;
				}
			}else{
				dataShow=dataPriv.access(elem,"fxshow",{display:restoreDisplay});
			}

			// Store hidden/visible for toggle so `.stop().toggle()` "reverses"
			if ( toggle ){
				dataShow.hidden=!hidden;
			}

			// 动效执行前显示元素
			if ( hidden ){
				showHide([elem],true);
			}

			// 动效结束根据配置隐藏元素,清空fxshow私有数据,调整样式为页面元素的原始值(如width样式)
			anim.done(function(){
				if ( !hidden ){
					showHide([elem]);
				}
				dataPriv.remove(elem,"fxshow");
				for ( prop in orig ){
					jQuery.style(elem,prop,orig[prop]);
				}
			} );
		}

		// Per-property setup
		propTween=createTween(hidden?dataShow[prop]:0,prop,anim);
		if ( !( prop in dataShow ) ){
			dataShow[prop]=propTween.start;
			if ( hidden ){
				propTween.end=propTween.start;
				propTween.start=0;
			}
		}
	}
}

// 设置需要特别配置缓动效果的样式,props键值对中值以数组形式保存样式终值、缓动效果
// specialEasing获取针对某个样式设置的特殊缓动效果对象,props获取样式终值
function propFilter(props,specialEasing){
	var index, name, easing, value, hooks;

	for ( index in props ){
		name=jQuery.camelCase(index);
		easing=specialEasing[name];
		value=props[index];
		if ( jQuery.isArray(value) ){
			easing=value[1];
			value=props[index]=value[0];
		}

		if ( index!==name ){
			props[name]=value;
			delete props[index];
		}

		hooks=jQuery.cssHooks[name];
		if ( hooks && "expand" in hooks ){
			value=hooks.expand(value);
			delete props[name];

			for ( index in value ){
				if ( !(index in props) ){
					props[index]=value[index];
					specialEasing[index]=easing;
				}
			}
		}else{
			specialEasing[name]=easing;
		}
	}
}

// properties对象形式保存最终样式信息,options包含回调、时长、缓动效果等
// 动画执行类,tick方法定时函数获取逐次执行样式值,stop中止动画,本身是延迟对象挂载回调函数
function Animation(elem,properties,options){
	var result, stopped, index=0, length=Animation.prefilters.length,
		deferred=jQuery.Deferred().always(function(){
			delete tick.elem;
		}),
		// 定时任务改变样式
		tick=function(){
			if ( stopped ){
				return false;
			}
			var currentTime=fxNow || createFxNow(),
				remaining=Math.max(0,animation.startTime+animation.duration-currentTime ),

				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]);

			// 返回值用于判断动效是否执行完成,执行完成清除$.timers中相应的timer
			if ( percent<1 && length ){
				return remaining;
			}else{
				deferred.resolveWith(elem,[animation]);
				return false;
			}
		},
		animation=deferred.promise({
			elem:elem,
			props:jQuery.extend({},properties),// 对象形式样式终值
			opts:jQuery.extend(true,{// 包含用户配置complete、done、always、fail。progress回调
				specialEasing:{},// 针对某个样式设置的缓动效果
				easing:jQuery.easing._default// 默认缓动效果
			},options),
			originalProperties:properties,// 原始终值样式配置
			originalOptions:options,// 原始时长、缓动、回调配置
			startTime:fxNow || createFxNow(),// 动效起始时间
			duration:options.duration,// 动效持续时间
			tweens:[],
			// 通过外部函数createTween调用animation.createTween方法创建tween,tween.run在定时器的timer调用tick方法执行
			// tween添加到当前animation对象的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;
			},
			// 向外提供中止动画的接口,$ele.finish方法中使用,或者回调函数中使用;gotoEnd为真触发回调函数
			stop:function(gotoEnd){
				var index=0,
					length=gotoEnd ? animation.tweens.length : 0;

				if ( stopped ){
					return this;
				}
				stopped=true;
				for ( ; index<length ; index++ ){
					animation.tweens[index].run(1);// tween.run方法执行到动画100%完成状态,当参数gotoEnd为真时
				}

				if ( gotoEnd ){
					deferred.notifyWith(elem,[animation,1,0]);
					deferred.resolveWith(elem,[animation,gotoEnd]);
				}else{
					deferred.rejectWith(elem,[animation,gotoEnd]);
				}
				return this;
			}
		} ),
		props=animation.props;// 对象形式样式终值

	// animation.opts.specialEasing获取针对某个样式设置的特殊缓动效果对象,props获取样式终值
	propFilter(props,animation.opts.specialEasing);

	for ( ; index<length ; index++ ){
		// 调用属性预处理函数,上下文为当前延迟对象animation
		result=Animation.prefilters[index].call(animation,elem,props,animation.opts);
		if ( result ){
			if ( jQuery.isFunction(result.stop) ){
				jQuery._queueHooks(animation.elem,animation.opts.queue).stop=
					jQuery.proxy(result.stop,result);
			}
			return result;
		}
	}

	// 创建样式处理类tween,tween.run方法修改样式,每个待修改的样式设置一个tween
	// props中不包含show、hide方法的处理内容,其已通过Animation.prefilters[index]处理
	jQuery.map(props,createTween,animation);

	if ( jQuery.isFunction(animation.opts.start) ){
		animation.opts.start.call(elem,animation);
	}

	// 调用requestAnimationFrame或setInterval执行定时动效,timer挂载在$.timers进行管理
	jQuery.fx.timer(
		jQuery.extend(tick,{
			elem:elem,
			anim:animation,
			queue:animation.opts.queue
		})
	);

	// 动效完成后,执行用户配置的回调
	return animation.progress(animation.opts.progress)
		.done(animation.opts.done,animation.opts.complete)
		.fail(animation.opts.fail)
		.always(animation.opts.always);
}

jQuery.Animation=jQuery.extend(Animation,{
	// 通过$.animation调用外部函数creatTween的方式,继而调用animation.creatTween的方式实现
	// 其主要目的是对外提供接口方便添加Animation.tweeners
	tweeners:{
		"*":[function(prop,value){
			var tween=this.createTween(prop,value);
			// 调整样式,通过获取tween处理后的样式赋值给元素实现
			adjustCSS(tween.elem,prop,rcssNum.exec(value),tween);
			return tween;
		}]
	},
	// 向Animation.tweeners添加定时处理样式函数
	tweener:function(props,callback){
		if ( jQuery.isFunction(props) ){
			callback=props;
			props=["*"];
		} else {
			props=props.match(rnotwhite);
		}

		var prop,
			index=0,
			length=props.length;

		for ( ; index<length ; index++ ){
			prop=props[index];
			Animation.tweeners[prop]=Animation.tweeners[prop] || [];
			Animation.tweeners[prop].unshift(callback);
		}
	},
	// 数组形式存储属性预处理函数,defaultPrefilter默认的预处理函数
	prefilters:[defaultPrefilter],
	// 添加属性预处理函数
	prefilter:function(callback,prepend){
		if ( prepend ){
			Animation.prefilters.unshift(callback);
		}else{
			Animation.prefilters.push(callback);
		}
	}
} );

// 回调、缓动、时长包装为对象后传入Animation对象
jQuery.speed=function(speed,easing,fn ){
	var opt=speed && typeof speed==="object" ? jQuery.extend({},speed) : {
		complete:fn || !fn && easing || jQuery.isFunction(speed) && speed,
		duration:speed,
		easing:fn && easing || easing && !jQuery.isFunction(easing) && easing
	};

	if ( jQuery.fx.off || document.hidden ){
		opt.duration=0;
	}else{
		opt.duration=typeof opt.duration==="number" ?
			opt.duration : opt.duration in jQuery.fx.speeds ?
				jQuery.fx.speeds[opt.duration] : jQuery.fx.speeds._default;
	}

	if ( opt.queue==null || opt.queue===true ){
		opt.queue="fx";
	}

	opt.old = opt.complete;

	opt.complete=function(){
		if ( jQuery.isFunction(opt.old) ){
			opt.old.call(this);
		}

		if ( opt.queue ){
			jQuery.dequeue(this,opt.queue);// 队列中取出最末一个函数
		}
	};

	return opt;
};

jQuery.fn.extend({
	// 隐藏元素透明度改变动效
	fadeTo:function(speed,to,easing,callback){
		// 隐藏的元素设置opaticy=0后,执行动效调整显示状况
		return this.filter(isHiddenWithinTree).css("opacity",0).show()
			.end().animate({opacity: to},speed,easing,callback);
	},

	// prop动效属性终值css对象形式,speed动效市场,easing缓动效果,callback回调函数
	animate: function(prop,speed,easing,callback){
		var empty=jQuery.isEmptyObject(prop),
			// 回调、缓动、时长包装为对象
			optall=jQuery.speed(speed,easing,callback),
			doAnimation=function(){
				var anim=Animation(this,jQuery.extend({},prop),optall);

				// 待改变的样式为空对象,或者动效finish方法执行过程中
				if ( empty || dataPriv.get(this,"finish") ){
					anim.stop(true);
				}
			};
			doAnimation.finish=doAnimation;

		return empty || optall.queue===false ? this.each(doAnimation) : this.queue(optall.queue,doAnimation);
	},

	// 中止动画,clearQueue为真时清空队列,gotoEnd执行回调函数
	stop:function(type,clearQueue,gotoEnd){
		// 队列函数执行完成,根据gotoEnd触发回调
		var stopQueue=function(hooks){
			var stop=hooks.stop;
			delete hooks.stop;
			stop(gotoEnd);
		};

		if ( typeof type!=="string" ){
			gotoEnd=clearQueue;
			clearQueue=type;
			type=undefined;
		}
		if ( clearQueue && type!==false ){
			this.queue(type || "fx",[]);// 数组时队列函数重新赋值,非数组则添加队列函数
		}

		return this.each(function(){
			var dequeue=true,
				index=type!=null && type+"queueHooks",
				timers=jQuery.timers,
				data=dataPriv.get(this);// 获取私有数据,data[index]私有数据中的队列函数

			if ( index ){
				if ( data[index] && data[index].stop ){
					stopQueue(data[index]);
				}
			}else{
				for ( index in data ){
					if ( data[index] && data[index].stop && rrun.test(index) ){
						stopQueue(data[index]);
					}
				}
			}

			// 终止动画,且参数type相关的队列函数记录不执行标志dequue=false
			for ( index=timers.length; index--; ){
				if ( timers[index].elem===this && (type==null || timers[index].queue===type) ){
					timers[index].anim.stop(gotoEnd);
					dequeue=false;
					timers.splice(index,1);
				}
			}

			// dequeue方法取出队列中的函数并执行,执行的是与参数type无关的队列函数
			if ( dequeue || !gotoEnd ){
				jQuery.dequeue(this,type);
			}
		} );
	},

	// 移除动画,清空queue队列,触发queue队列完成回调函数,调用timer.anim.stop停止动画
	finish:function(type){
		if ( type!==false ){
			type=type || "fx";
		}
		return this.each( function(){
			var index,
				data=dataPriv.get(this),
				queue=data[type+"queue"],
				hooks=data[type+"queueHooks"],
				timers=jQuery.timers,
				length=queue ? queue.length : 0;

			data.finish=true;// 当前元素的dataPriv中添加finish标记,调用animation方法时判断是否finish方法执行过程中

			// 清空queue方法注册的队列函数,并执行其回调
			jQuery.queue(this,type,[]);

			if ( hooks && hooks.stop ){
				hooks.stop.call(this,true);
			}

			// 判断当前元素定时执行函数对象timer,若是,调用该对象的anim.stop中止动画,同时移除该timer
			for ( index=timers.length; index--; ){
				if ( timers[index].elem===this && timers[index].queue===type ){
					timers[index].anim.stop(true);
					timers.splice(index,1);
				}
			}

			for ( index=0; index<length; index++ ){
				if ( queue[index] && queue[index].finish ){
					queue[index].finish.call(this);
				}
			}

			// 动画移除完毕标记
			delete data.finish;
		} );
	}
} );

// 设置$ele.toggle|show|hide方法
jQuery.each(["toggle","show","hide"],function(i,name){
	var cssFn=jQuery.fn[name];
	jQuery.fn[name]=function(speed,easing,callback){
		return speed==null || typeof speed==="boolean" ?
			cssFn.apply(this,arguments) :
			this.animate( genFx(name,true), speed, easing, callback );
	};
});

// 设置$ele.slideDown|slideUp|slideToggle|fadeIn|fadeOut|fadeToggle方法
jQuery.each({
	slideDown:genFx("show"),
	slideUp:genFx("hide"),
	slideToggle:genFx("toggle"),
	fadeIn:{opacity:"show"},
	fadeOut:{opacity:"hide"},
	fadeToggle:{opacity:"toggle"}
},function(name,props){
	jQuery.fn[name]=function(speed,easing,callback){
		return this.animate(props,speed,easing,callback);
	};
});

// 存储待执行的动效定时任务,数组形式存储多个,意味同时可以执行多个动效
jQuery.timers=[];
// 过渡方法,用于执行Animation动效的tick方法,执行完成后清空相应的timer
jQuery.fx.tick=function(){
	var timer,
		i=0,
		timers=jQuery.timers;

	fxNow=jQuery.now();

	for ( ; i<timers.length; i++ ){
		timer=timers[i];

		// 首先跑动效的tick方法,动效执行完成后,移除相应的timers,避免动效反复执行
		if ( !timer() && timers[i]===timer ){
			timers.splice(i--,1);
		}
	}

	if ( !timers.length ){
		jQuery.fx.stop();
	}
	fxNow=undefined;
};

// jQuery.timers中添加定时任务,未执行完成启动执行,执行完成移除定时任务
jQuery.fx.timer=function(timer){
	jQuery.timers.push(timer);
	if ( timer() ){
		jQuery.fx.start();
	}else{
		jQuery.timers.pop();
	}
};

jQuery.fx.interval=13;
// 跑定时任务,执行动效
jQuery.fx.start=function(){
	if ( !timerId ){
		timerId=window.requestAnimationFrame ?
			window.requestAnimationFrame(raf) :
			window.setInterval(jQuery.fx.tick,jQuery.fx.interval);
	}
};

// 移除定时任务
jQuery.fx.stop=function(){
	if ( window.cancelAnimationFrame ){
		window.cancelAnimationFrame(timerId);
	}else{
		window.clearInterval(timerId);
	}

	timerId=null;
};

jQuery.fx.speeds={
	slow:600,
	fast:200,
	_default:400
};

return jQuery;
});

2.Tween.js

生成定时器处理内容,run方法跑任务获取每次定时任务的样式值,通过adjustCss方法赋值给元素,用户可以添加自定义模式改写jquery实现方案。

define([
	"../core",
	"../css"
],function(jQuery){

function Tween(elem,options,prop,end,easing){
	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;// 执行动效的dom对象
		this.prop=prop;// 样式名
		this.easing=easing || jQuery.easing._default;// 缓动效果
		this.options=options;
		this.start=this.now=this.cur();
		this.end=end;
		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 hooks=Tween.propHooks[this.prop];

		this.pos=this.options.duration ? jQuery.easing[this.easing](percent) : percent;
		this.now=(this.end-this.start)*this.pos+this.start;

		if ( this.options.step ){
			this.options.step.call(this.elem,this.now,this);
		}

		if ( hooks && hooks.set ){// 特殊方式设置样式
			hooks.set(this);
		}else{
			Tween.propHooks._default.set(this);
		}
		return this;
	}
};

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

Tween.propHooks={
	_default:{
		// 获取当前样式值
		get:function(tween){
			var result;

			// tween.elem非节点元素或tween.elem[tween.prop]非空,获取tween.elem[tween.prop]
			if ( tween.elem.nodeType!==1 ||
				tween.elem[tween.prop]!=null && tween.elem.style[tween.prop]==null ){
				return tween.elem[tween.prop];// 
			}

			// 获取样式,"10px"转化为数值型10输出
			result=jQuery.css(tween.elem,tween.prop,"");

			return !result || result==="auto" ? 0 : result;
		},
		// 设置样式或属性
		set:function(tween){

			if ( jQuery.fx.step[tween.prop] ){
				jQuery.fx.step[tween.prop](tween);
			}else if( tween.elem.nodeType===1 &&
				( tween.elem.style[jQuery.cssProps[tween.prop]]!=null ||
					jQuery.cssHooks[tween.prop] ) ){
				jQuery.style(tween.elem,tween.prop,tween.now+tween.unit);
			}else{
				tween.elem[tween.prop]=tween.now;
			}
		}
	}
};

Tween.propHooks.scrollTop=Tween.propHooks.scrollLeft={// 父节点存在时设置
	set: function(tween){
		if ( tween.elem.nodeType && tween.elem.parentNode ){
			tween.elem[tween.prop]=tween.now;
		}
	}
};

// 缓动效果设定
jQuery.easing={
	linear:function(p){
		return p;
	},
	swing:function(p){
		return 0.5-Math.cos(p*Math.PI)/2;
	},
	_default:"swing"
};

jQuery.fx=Tween.prototype.init;

jQuery.fx.step={};

});

猜你喜欢

转载自schifred.iteye.com/blog/2334271