三、doAnimation
内部的Animation()
方法
作用:$().animate()
核心方法
源码:
//animate()核心方法
//源码7844行
//elem:目标元素
//this:目标元素
//{'width': '500'}
// optall={
// complete:function(){jQuery.dequeue()},
// old:false,
// duration: 400,
// easing: undefined,
// queue:"fx",
// }
function Animation( elem, properties, options ) {
var result,
stopped,
index = 0,
//1
length = Animation.prefilters.length,
//{
// always:function(){},
// catch:function(){},
// done:function(){},
// xxx
// }
//初始化deferred对象
//deferred.always()表示不管成功还是失败,最终都会运行内部设置的代码
deferred = jQuery.Deferred().always( function() {
// Don't match elem in the :animated selector
delete tick.elem;
} ),
tick = function() {
if ( stopped ) {
return false;
}
var currentTime = fxNow || createFxNow(),
remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
// Support: Android 2.3 only
// 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 there's more to do, yield
if ( percent < 1 && length ) {
return remaining;
}
// If this was an empty animation, synthesize a final progress notification
if ( !length ) {
deferred.notifyWith( elem, [ animation, 1, 0 ] );
}
// Resolve the animation and report its conclusion
deferred.resolveWith( elem, [ animation ] );
return false;
},
//==========tick end==========
//让animation带有promise的属性,并在其中添加动画的属性和方法
animation = deferred.promise( {
elem: elem,
props: jQuery.extend( {}, properties ),
opts: jQuery.extend( true, {
specialEasing: {},
easing: jQuery.easing._default
}, options ),
originalProperties: properties,
originalOptions: options,
startTime: fxNow || createFxNow(),
duration: options.duration,
tweens: [],
//500,'width',animation
createTween: function( prop, end ) {
var tween = jQuery.Tween( elem, animation.opts, prop, end,
animation.opts.specialEasing[ prop ] || animation.opts.easing );
animation.tweens.push( tween );
// {
// easing: "swing"
// elem: div#A
// end: 500
// now: 500
// options: {specialEasing: {…}, easing: "swing", complete: ƒ, duration: 400, queue: "fx", …}
// pos: 1
// prop: "width"
// start: 100
// unit: "px"
// }
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.notifyWith( elem, [ animation, 1, 0 ] );
deferred.resolveWith( elem, [ animation, gotoEnd ] );
} else {
deferred.rejectWith( elem, [ animation, gotoEnd ] );
}
return this;
}
} ),
//===========animation end===============
props = animation.props;
//{width:500},undefined
propFilter( props, animation.opts.specialEasing );
for ( ; index < length; index++ ) {
result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts );
if ( result ) {
if ( isFunction( result.stop ) ) {
jQuery._queueHooks( animation.elem, animation.opts.queue ).stop =
result.stop.bind( result );
}
return result;
}
}
/*运行动画*/
// createTween(500,'width',animation)
jQuery.map( props, createTween, animation );
if ( isFunction( animation.opts.start ) ) {
animation.opts.start.call( elem, animation );
}
// Attach callbacks from options
animation
.progress( animation.opts.progress )
.done( animation.opts.done, animation.opts.complete )
.fail( animation.opts.fail )
.always( animation.opts.always );
jQuery.fx.timer(
//让tick方法继承elem、anim和queue属性
jQuery.extend( tick, {
elem: elem,
anim: animation,
queue: animation.opts.queue
} )
);
return animation;
}
解析:
(1)Animation.prefilters
源码:
jQuery.Animation = jQuery.extend( Animation, {
//源码8175行
//defaultPrefilter是一个function
prefilters: [ defaultPrefilter ],
})
所以Animation.prefilters=1
,defaultPrefilter
的源码暂不解析
(2)关于jQuery.Deferred()
的解释,请参考:jQuery中的Deferred详解和使用
(3)jQuery.map(elems, callback, arg)
作用:
根据elems
数量,循环运行callback( elems[ i ], i, arg )
源码:
jQuery.extend( {
// arg is for internal usage only
//源码524行
//props, createTween, animation
map: function( elems, callback, arg ) {
var length, value,
i = 0,
ret = [];
// Go through the array, translating each of the items to their new values
//如果elems是类数组的话
if ( isArrayLike( elems ) ) {
length = elems.length;
for ( ; i < length; i++ ) {
value = callback( elems[ i ], i, arg );
if ( value != null ) {
ret.push( value );
}
}
// Go through every key on the object,
} else {
//走这边
for ( i in elems ) {
//500 width animation
/*执行动画*/
value = callback( elems[ i ], i, arg );
if ( value != null ) {
ret.push( value );
}
}
}
console.log(ret,'ret555')
// Flatten any nested arrays
// 展平任何嵌套数组
return concat.apply( [], ret );
},
})
解析:
根据例子的话,就是:
createTween(500,'width',animation)
createTween(300,'width',animation)
createTween(1000,'width',animation)
(4)jQuery内部函数createTween(value, prop, animation)
作用:
让animation
调用Animation.tweeners[ "*" ]
中的方法
源码:
//源码7752行
//创建动画对象
// createTween(500,'width',animation)
function createTween( value, prop, animation ) {
var tween,
//[ function( prop, value ) {
// var tween = this.createTween( prop, value );
// console.log('vvvv','aaa8083')
// adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween );
// return tween;
// } ]
collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ),
index = 0,
//1
length = collection.length;
for ( ; index < length; index++ ) {
//prop:width
//value:500
//运行collection[ index ],this绑定animation
if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) {
// We're done with this property
return tween;
}
}
}
(5)Animation.tweeners[ "*
" ]
作用:
让animation
调用Animation.tweeners[ "*" ]
中的方法
jQuery.Animation = jQuery.extend( Animation, {
//源码8152行
tweeners: {
//prop:width
//value:500
"*": [ function( prop, value ) {
//animation.createTween
var tween = this.createTween( prop, value );
adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween );
return tween;
} ]
},
})
解析:
返回经过animation. createTween('width',500)
处理和adjustCSS()
处理的变量tween
① animation. createTween('width',500)animation
是Animation()
方法中封装的一个对象(对象key
的value
是function
)
作用:
根据开发者传入的属性,将其转化为一个对象,对象内部的属性时执行动画所需要的属性。
源码:
animation = deferred.promise( {
//500,'width',animation
createTween: function( prop, end ) {
var tween = jQuery.Tween( elem, animation.opts, prop, end,
animation.opts.specialEasing[ prop ] || animation.opts.easing );
animation.tweens.push( tween );
// {
// easing: "swing"
// elem: div#A
// end: 500
// now: 500
// options: {specialEasing: {…}, easing: "swing", complete: ƒ, duration: 400, queue: "fx", …}
// pos: 1
// prop: "width"
// start: 100
// unit: "px"
// }
return tween;
},
})
解析:
调用jQuery.Tween
获得tween
对象,并把tween
对象放进animation.tweens
数组中
② 简单看下jQuery.Tween
源码:
//源码7568行
function Tween( elem, options, prop, end, easing ) {
//width 500 swing
//width 300 swing
//width 1000 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 || jQuery.easing._default;
this.options = options;
this.start = this.now = this.cur();
this.end = end;
this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
},
cur: function() {},
run: function( percent ) {},
};
Tween.prototype.init.prototype = Tween.prototype;
执行jQuery.Tween
方法,就是new
一个对象,就是执行jQuery.Tween.init()
方法,根据{width:500}
生成的动画对象如下:
{
easing: "swing"
elem: div#A
end: 500
now: 500
options: {specialEasing: {…}, easing: "swing", complete: ƒ, duration: 400, queue: "fx", …}
pos: 1
prop: "width"
start: 100
unit: "px"
}
③ 关于adjustCSS
的解析,请看:jQuery源码解析(4)—— css样式、定位属性
Animation.tweeners[ "*" ]
方法最终返回的tween
如下:
{
easing: "swing"
elem: div#A
end: 500
now: 500
options: {specialEasing: {…}, easing: "swing", complete: ƒ, duration: 400, queue: "fx", …}
pos: 1
prop: "width"
start: 100
unit: "px"
}
综上,jQuery.map()
的最终作用就是将$().animate()
中的参数转化为动画对象,并push
进animation.tweens
数组中
(6)jQuery.fx.timer()
作用:
依次执行timer
源码:
//源码8504行
//单个动画内部执行
jQuery.fx.timer = function( timer ) {
//将Animation.tick()依次放进jQuery.timers数组中
jQuery.timers.push( timer );
//每push进一个,就运行一个
jQuery.fx.start();
};
jQuery.timers
是一个数组:
//源码8431行
jQuery.timers = [];
(7)jQuery.fx.start()
作用:
在动画运行前,加锁,并运行动画
源码:
//源码8514行
//加锁,运行动画
jQuery.fx.start = function() {
if ( inProgress ) {
return;
}
//动画开始即为运行中,加上锁
inProgress = true;
//运行
schedule();
};
注意:inProgress 锁是控制整个动画流程的锁,而不是单个动画队列的锁
(8)schedule()
作用:
如果动画已经开始(inProgress
=true),那么就不断执行jQuery.fx.tick()方法(动画渲染)
源码:
//源码7694行
//如果动画已经开始,那么就不断执行jQuery.fx.tick()方法(动画渲染)
function schedule() {
//inProgress是判断整个动画流程是否结束的标志
//当inProgress=null时,整个动画结束
if ( inProgress ) {
//走这边
if ( document.hidden === false && window.requestAnimationFrame ) {
//使用requestAnimationFrame来完成动画
//递归
window.requestAnimationFrame( schedule );
} else {
//13代表动画每秒运行的帧数,可以保证浏览器能完成动画
//jQuery.fx.interval = 13;
window.setTimeout( schedule, jQuery.fx.interval );
}
/*执行动画*/
jQuery.fx.tick();
}
}
(9)jQuery.fx.tick()
作用:
运行Animation.tick()
并安全地移除它
源码:
//源码8483行
//运行Animation.tick()并安全地移除它
jQuery.fx.tick = function() {
var timer,
i = 0,
timers = jQuery.timers;
fxNow = Date.now();
//这里的timers,就是Animation.tick()的集合
for ( ; i < timers.length; i++ ) {
timer = timers[ i ];
// Run the timer and safely remove it when done (allowing for external removal)
//运行Animation.tick()并安全地移除它
if ( !timer() && timers[ i ] === timer ) {
timers.splice( i--, 1 );
}
}
//inProgress=null,停止动画
if ( !timers.length ) {
jQuery.fx.stop();
}
fxNow = undefined;
};
//源码8474行
//结束整个动画流程
jQuery.fx.stop = function() {
inProgress = null;
};
(10)Animation.tick()
作用:
根据动画的参数来执行动画
源码:
function Animation( elem, properties, options ) {
//根据动画的参数来执行动画
tick = function() {
if ( stopped ) {
return false;
}
//当前时间的时间戳
var currentTime = fxNow || createFxNow(),
//动画时长默认400ms
//开始时间+动画时长-当前时间
//在每次调用requestAnimationFrame后,记录下剩下的的时间在总时间(duration)中的位置
remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
// Support: Android 2.3 only
// 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 there's more to do, yield
if ( percent < 1 && length ) {
return remaining;
}
// If this was an empty animation, synthesize a final progress notification
if ( !length ) {
deferred.notifyWith( elem, [ animation, 1, 0 ] );
}
// Resolve the animation and report its conclusion
deferred.resolveWith( elem, [ animation ] );
return false;
},
}
解析:
通过动画持续时间duration
、动画开始时间animation.startTime
和每次调用requestAnimationFrame
后动画结束时间currentTime
,计算出此帧在整个动画流程中的占比,从而较为准确绘制动画
(11)Tween.run()
作用:
绘制动画帧
源码:
Tween.prototype = {
run: function( percent ) {
// {
// easing: "swing"
// elem: div#A
// end: 500
// now: 105.52601592046467
// options: {specialEasing: {…}, easing: "swing", complete: ƒ, duration: 400, queue: "fx", …}
// pos: 1
// prop: "width"
// start: 100
// unit: "px"
// }
var eased,
//undefiend
hooks = Tween.propHooks[ this.prop ];
//400
if ( this.options.duration ) {
//swing,两边慢中间快
//动画效果
this.pos = eased = jQuery.easing[ this.easing ](
percent, this.options.duration * percent, 0, 1, this.options.duration
);
} else {
this.pos = eased = percent;
}
//width的宽度
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 {
//走这边
//执行style变化
Tween.propHooks._default.set( this );
}
return this;
},
}
解析:
一个是动画效果swing
的处理:jQuery.easing[ this.easing ](percent, this.options.duration * percent, 0, 1, this.options.duration);
另一个就是关键的style
变化了:Tween.propHooks._default.set( this )
(12)Tween.propHooks._default.set()
作用:
执行style
变化
源码:
Tween.propHooks = {
_default: {
//源码7661行
set: function( tween ) {
// Use step hook for back compat.
// Use cssHook if its there.
// Use .style if available and use plain properties where available.
//undefined
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 ] ) ) {
//走这边
//#A,width,100px(103px,134px,xxx)
jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
} else {
tween.elem[ tween.prop ] = tween.now;
}
},
}
}
解析:tween.now
,是每次requestAnimationFrame
要变化的width
的值,tween.unit
是px
,所以这段代码最终执行的是jQuery.style( 目标元素, 要变化的style属性, 要变化的值 )
(13)jQuery.style()
作用:
设置 DOM 节点的 style 属性
简略的源码:
// Get and set the style property on a DOM Node
//源码7279行
style: function( elem, name, value, extra ) {
elem.style[ name ] = value
}
综上,Animation() 有两大作用:
(1)将传入的动画对象处理成jQuery的动画对象。
(2)根据duration
的间隔,利用requestAnimationFrame
循环执行style
,从而达到渲染动画的目的。
最后,附上 doAnimation() 的流程图,建议配合整个$().animate()的流程图(二、的最后一个图)
一起看:
下篇将会模拟实现$
().animate() 方法,敬请期待!
(完)