javascript - Where does jQuery do animations/timers in `$.queue()`? -
i looking through source of jquery (specifically queue() function) , saw in puts function .data() object associated element:
queue: function( elem, type, data ) { var q; if ( elem ) { type = ( type || "fx" ) + "queue"; q = jquery._data( elem, type ); // speed dequeue getting out if lookup if ( data ) { if ( !q || jquery.isarray(data) ) { q = jquery._data( elem, type, jquery.makearray(data) ); } else { q.push( data ); } } return q || []; } } now looking @ ._data .data() fourth argument of true, timers or animations being set? or function calls matter:
data: function( elem, name, data, pvt /* internal use */ ) { if ( !jquery.acceptdata( elem ) ) { return; } var privatecache, thiscache, ret, internalkey = jquery.expando, getbyname = typeof name === "string", // have handle dom nodes , js objects differently because ie6-7 // can't gc object references across dom-js boundary isnode = elem.nodetype, // dom nodes need global jquery cache; js object data // attached directly object gc can occur automatically cache = isnode ? jquery.cache : elem, // defining id js objects if cache exists allows // code shortcut on same path dom node no cache id = isnode ? elem[ internalkey ] : elem[ internalkey ] && internalkey, isevents = name === "events"; // avoid doing more work need when trying data on // object has no data @ if ( (!id || !cache[id] || (!isevents && !pvt && !cache[id].data)) && getbyname && data === undefined ) { return; } if ( !id ) { // dom nodes need new unique id each element since data // ends in 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 object // serialized using json.stringify if ( !isnode ) { cache[ id ].tojson = jquery.noop; } } // object can passed jquery.data instead of key/value pair; gets // shallow copied on onto 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() stored in separate object inside object's internal data // cache in order avoid key collisions between internal data , 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 inspect internal events object using jquery.data, // undocumented , subject change. listen? no. if ( isevents && !thiscache[ name ] ) { return privatecache.events; } // check both converted-to-camel , non-converted data property names // if data property specified if ( getbyname ) { // first try find as-is property data ret = thiscache[ name ]; // test null|undefined property data if ( ret == null ) { // try find camelcased property ret = thiscache[ jquery.camelcase( name ) ]; } } else { ret = thiscache; } return ret; } edit 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 complex beast (see reference below). starts normalizing parameters, , jumps doanimation function callback used jquery's queue method. jquery queue's animations happen in order. queue doesn't animate itself, acts trigger code performs animation.
at end of doanimation, there loop animate every relevant property in animation (lines 8576 - 8618, v1.7.2). first inner-line of loop instantiates new jquery.fx object:
e = new jquery.fx( this, opt, p ); at end of loop, e.custom(...) called in couple places. important function. if through jquery.fx.prototype.custom (see reference below), you'll find:
if ( t() && jquery.timers.push(t) && !timerid ) { timerid = setinterval( fx.tick, fx.interval ); } the line setinterval jquery's animation heartbeat started. points jquery.fx.tick (see reference below) iterates through every timer in jquery.timers. if @ snippet above, you'll notice part of if statement involves pushing t stack of timers. t set in jquery.fx.prototype.custom as:
function t( gotoend ) { return self.step( gotoend ); } and right there jquery's animation happens.
reference
jquery's animate function (lines 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 ] ); } // not change referenced properties per-property easing lost prop = jquery.extend( {}, prop ); function doanimation() { // xxx 'this' not have nodename when running // test suite if ( optall.queue === false ) { jquery._mark( ); } 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; // store per property easing , used determine when animation complete opt.animatedproperties = {}; // first pass on propertys expand / normalize ( 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, wont overwrite keys present. // - reusing 'p' above because have correct "name" ( p in replace ) { if ( ! ( p in prop ) ) { prop[ p ] = replace[ p ]; } } } } ( 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( ); } if ( iselement && ( name === "height" || name === "width" ) ) { // make sure nothing sneaks out // record 3 overflow attributes because ie not // change overflow attribute when overflowx , // overflowy set same value opt.overflow = [ this.style.overflow, this.style.overflowx, this.style.overflowy ]; // set display property inline-block height/width // animations on inline elements 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 inline 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"; } ( p in prop ) { e = new jquery.fx( this, opt, p ); val = prop[ p ]; if ( rfxtypes.test( val ) ) { // tracks whether show or hide based on private // data attached 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" ); // need 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 +=/-= token provided, we're doing relative animation if ( parts[1] ) { end = ( (parts[ 1 ] === "-=" ? -1 : 1) * end ) + start; } e.custom( start, end, unit ); } else { e.custom( start, val, "" ); } } } // 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):
e = new jquery.fx( this, opt, p ); jquery.fx.prototype.custom (lines 8806 - 8836, v1.7.2):
// start animation 1 number 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):
tick: function() { var timer, timers = jquery.timers, = 0; ( ; < timers.length; i++ ) { timer = timers[ ]; // checks timer has not been removed if ( !timer() && timers[ ] === timer ) { timers.splice( i--, 1 ); } } if ( !timers.length ) { jquery.fx.stop(); } }
Comments
Post a Comment