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={}; });