[英]Where does jQuery do animations/timers in `$.queue()`?
I was looking through the source of jQuery (specifically the queue()
function) and saw that in just puts the function into a the .data()
object associated with that element: 我查看了jQuery的源代码(特别是
queue()
函数),发现只是将函数放入与该元素关联的.data()
对象中:
queue: function( elem, type, data ) {
var q;
if ( elem ) {
type = ( type || "fx" ) + "queue";
q = jQuery._data( elem, type );
// Speed up dequeue by getting out quickly if this is just a lookup
if ( data ) {
if ( !q || jQuery.isArray(data) ) {
q = jQuery._data( elem, type, jQuery.makeArray(data) );
} else {
q.push( data );
}
}
return q || [];
}
}
Now looking at the ._data
which is just .data()
with a fourth argument of true, where are any timers or animations being set? 现在查看只是
.data()
且第四个参数为true的._data
,在哪里设置了计时器或动画? Or any function calls for that matter: 或任何函数都需要这样做:
data: function( elem, name, data, pvt /* Internal Use Only */ ) {
if ( !jQuery.acceptData( elem ) ) {
return;
}
var privateCache, thisCache, ret,
internalKey = jQuery.expando,
getByName = typeof name === "string",
// We have to handle DOM nodes and JS objects differently because IE6-7
// can't GC object references properly across the DOM-JS boundary
isNode = elem.nodeType,
// Only DOM nodes need the global jQuery cache; JS object data is
// attached directly to the object so GC can occur automatically
cache = isNode ? jQuery.cache : elem,
// Only defining an ID for JS objects if its cache already exists allows
// the code to shortcut on the same path as a DOM node with no cache
id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey,
isEvents = name === "events";
// Avoid doing any more work than we need to when trying to get data on an
// object that has no data at all
if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) {
return;
}
if ( !id ) {
// Only DOM nodes need a new unique ID for each element since their data
// ends up in the global cache
if ( isNode ) {
elem[ internalKey ] = id = ++jQuery.uuid;
} else {
id = internalKey;
}
}
if ( !cache[ id ] ) {
cache[ id ] = {};
// Avoids exposing jQuery metadata on plain JS objects when the object
// is serialized using JSON.stringify
if ( !isNode ) {
cache[ id ].toJSON = jQuery.noop;
}
}
// An object can be passed to jQuery.data instead of a key/value pair; this gets
// shallow copied over onto the existing cache
if ( typeof name === "object" || typeof name === "function" ) {
if ( pvt ) {
cache[ id ] = jQuery.extend( cache[ id ], name );
} else {
cache[ id ].data = jQuery.extend( cache[ id ].data, name );
}
}
privateCache = thisCache = cache[ id ];
// jQuery data() is stored in a separate object inside the object's internal data
// cache in order to avoid key collisions between internal data and user-defined
// data.
if ( !pvt ) {
if ( !thisCache.data ) {
thisCache.data = {};
}
thisCache = thisCache.data;
}
if ( data !== undefined ) {
thisCache[ jQuery.camelCase( name ) ] = data;
}
// Users should not attempt to inspect the internal events object using jQuery.data,
// it is undocumented and subject to change. But does anyone listen? No.
if ( isEvents && !thisCache[ name ] ) {
return privateCache.events;
}
// Check for both converted-to-camel and non-converted data property names
// If a data property was specified
if ( getByName ) {
// First Try to find as-is property data
ret = thisCache[ name ];
// Test for null|undefined property data
if ( ret == null ) {
// Try to find the camelCased property
ret = thisCache[ jQuery.camelCase( name ) ];
}
} else {
ret = thisCache;
}
return ret;
}
EDIT for zzzzBov: 编辑zzzzBov:
animate: function( prop, speed, easing, callback ) {
var optall = jQuery.speed( speed, easing, callback );
if ( jQuery.isEmptyObject( prop ) ) {
return this.each( optall.complete, [ false ] );
....
return optall.queue === false ?
this.each( doAnimation ) :
this.queue( optall.queue, doAnimation );
jQuery's animate
method is a complex beast ( see reference below ). jQuery的
animate
方法是一个复杂的野兽( 请参阅下面的参考资料 )。 It starts by normalizing the parameters, and then immediately jumps into the doAnimation
function which is the callback used by jQuery's queue
method . 首先通过规范化参数开始,然后立即跳转到
doAnimation
函数,该函数是jQuery的queue
方法使用的回调。 jQuery queue's animations so they happen in order. jQuery队列的动画使它们按顺序发生。 The queue doesn't animate anything by itself, it simply acts as a trigger for the code that performs the animation.
队列本身不会设置任何动画,它只是充当执行动画的代码的触发器。
At the end of doAnimation
, there is a loop to animate every relevant property in the animation ( lines 8576 - 8618, v1.7.2 ). 在
doAnimation
的末尾,有一个循环为动画中的每个相关属性设置动画( 第8576至8618行,v1.7.2 )。 The first inner-line of the loop instantiates a new jQuery.fx
object: 循环的第一个内线实例化一个新的
jQuery.fx
对象:
e = new jQuery.fx( this, opt, p );
At the end of the loop, e.custom(...)
is called in a couple places. 在循环的最后,在几个地方调用了
e.custom(...)
。 This is the important function. 这是重要的功能。 If you look through
jQuery.fx.prototype.custom
( see reference below ), you'll find: 如果您浏览
jQuery.fx.prototype.custom
( 请参阅下面的参考资料 ),则会发现:
if ( t() && jQuery.timers.push(t) && !timerId ) {
timerId = setInterval( fx.tick, fx.interval );
}
The line with setInterval
is where jQuery's animation heartbeat is started. setInterval
行是jQuery动画心跳开始的地方。 This points to jQuery.fx.tick
( see reference below ) which iterates through every timer in jQuery.timers
. 这指向
jQuery.fx.tick
( 请参阅下面的参考资料 ),它会迭代jQuery.timers
每个计时器。 If you look at the snippet above, you'll notice that part of the if
statement involves pushing t
into the stack of timers. 如果您看一下上面的代码片段,您会注意到
if
语句的一部分涉及将t
推入定时器堆栈。 t
was set in jQuery.fx.prototype.custom
as: 在
jQuery.fx.prototype.custom
中将t
设置为:
function t( gotoEnd ) {
return self.step( gotoEnd );
}
And that right there is where jQuery's animation happens. jQuery动画就在那里发生。
jQuery's animate
function ( lines 8484 - 8627, v1.7.2 ): jQuery的
animate
功能( 第8484-8627行,v1.7.2 ):
animate: function( prop, speed, easing, callback ) {
var optall = jQuery.speed( speed, easing, callback );
if ( jQuery.isEmptyObject( prop ) ) {
return this.each( optall.complete, [ false ] );
}
// Do not change referenced properties as per-property easing will be lost
prop = jQuery.extend( {}, prop );
function doAnimation() {
// XXX 'this' does not always have a nodeName when running the
// test suite
if ( optall.queue === false ) {
jQuery._mark( this );
}
var opt = jQuery.extend( {}, optall ),
isElement = this.nodeType === 1,
hidden = isElement && jQuery(this).is(":hidden"),
name, val, p, e, hooks, replace,
parts, start, end, unit,
method;
// will store per property easing and be used to determine when an animation is complete
opt.animatedProperties = {};
// first pass over propertys to expand / normalize
for ( p in prop ) {
name = jQuery.camelCase( p );
if ( p !== name ) {
prop[ name ] = prop[ p ];
delete prop[ p ];
}
if ( ( hooks = jQuery.cssHooks[ name ] ) && "expand" in hooks ) {
replace = hooks.expand( prop[ name ] );
delete prop[ name ];
// not quite $.extend, this wont overwrite keys already present.
// also - reusing 'p' from above because we have the correct "name"
for ( p in replace ) {
if ( ! ( p in prop ) ) {
prop[ p ] = replace[ p ];
}
}
}
}
for ( name in prop ) {
val = prop[ name ];
// easing resolution: per property > opt.specialEasing > opt.easing > 'swing' (default)
if ( jQuery.isArray( val ) ) {
opt.animatedProperties[ name ] = val[ 1 ];
val = prop[ name ] = val[ 0 ];
} else {
opt.animatedProperties[ name ] = opt.specialEasing && opt.specialEasing[ name ] || opt.easing || 'swing';
}
if ( val === "hide" && hidden || val === "show" && !hidden ) {
return opt.complete.call( this );
}
if ( isElement && ( name === "height" || name === "width" ) ) {
// Make sure that nothing sneaks out
// Record all 3 overflow attributes because IE does not
// change the overflow attribute when overflowX and
// overflowY are set to the same value
opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ];
// Set display property to inline-block for height/width
// animations on inline elements that are having width/height animated
if ( jQuery.css( this, "display" ) === "inline" &&
jQuery.css( this, "float" ) === "none" ) {
// inline-level elements accept inline-block;
// block-level elements need to be inline with layout
if ( !jQuery.support.inlineBlockNeedsLayout || defaultDisplay( this.nodeName ) === "inline" ) {
this.style.display = "inline-block";
} else {
this.style.zoom = 1;
}
}
}
}
if ( opt.overflow != null ) {
this.style.overflow = "hidden";
}
for ( p in prop ) {
e = new jQuery.fx( this, opt, p );
val = prop[ p ];
if ( rfxtypes.test( val ) ) {
// Tracks whether to show or hide based on private
// data attached to the element
method = jQuery._data( this, "toggle" + p ) || ( val === "toggle" ? hidden ? "show" : "hide" : 0 );
if ( method ) {
jQuery._data( this, "toggle" + p, method === "show" ? "hide" : "show" );
e[ method ]();
} else {
e[ val ]();
}
} else {
parts = rfxnum.exec( val );
start = e.cur();
if ( parts ) {
end = parseFloat( parts[2] );
unit = parts[3] || ( jQuery.cssNumber[ p ] ? "" : "px" );
// We need to compute starting value
if ( unit !== "px" ) {
jQuery.style( this, p, (end || 1) + unit);
start = ( (end || 1) / e.cur() ) * start;
jQuery.style( this, p, start + unit);
}
// If a +=/-= token was provided, we're doing a relative animation
if ( parts[1] ) {
end = ( (parts[ 1 ] === "-=" ? -1 : 1) * end ) + start;
}
e.custom( start, end, unit );
} else {
e.custom( start, val, "" );
}
}
}
// For JS strict compliance
return true;
}
return optall.queue === false ?
this.each( doAnimation ) :
this.queue( optall.queue, doAnimation );
}
Instantiation of jQuery.fx
( line 8577, v1.7.2 ): 实例化
jQuery.fx
( 8577行,v1.7.2 ):
e = new jQuery.fx( this, opt, p );
jQuery.fx.prototype.custom
(lines 8806 - 8836, v1.7.2): jQuery.fx.prototype.custom
(第8806-8836行,v1.7.2):
// Start an animation from one number to another
custom: function( from, to, unit ) {
var self = this,
fx = jQuery.fx;
this.startTime = fxNow || createFxNow();
this.end = to;
this.now = this.start = from;
this.pos = this.state = 0;
this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" );
function t( gotoEnd ) {
return self.step( gotoEnd );
}
t.queue = this.options.queue;
t.elem = this.elem;
t.saveState = function() {
if ( jQuery._data( self.elem, "fxshow" + self.prop ) === undefined ) {
if ( self.options.hide ) {
jQuery._data( self.elem, "fxshow" + self.prop, self.start );
} else if ( self.options.show ) {
jQuery._data( self.elem, "fxshow" + self.prop, self.end );
}
}
};
if ( t() && jQuery.timers.push(t) && !timerId ) {
timerId = setInterval( fx.tick, fx.interval );
}
}
jQuery.fx.tick
( lines 8949 - 8965, v1.7.2 ): jQuery.fx.tick
( jQuery.fx.tick
行,v1.7.2 ):
tick: function() {
var timer,
timers = jQuery.timers,
i = 0;
for ( ; i < timers.length; i++ ) {
timer = timers[ i ];
// Checks the timer has not already been removed
if ( !timer() && timers[ i ] === timer ) {
timers.splice( i--, 1 );
}
}
if ( !timers.length ) {
jQuery.fx.stop();
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.