Jump To …

13-effects.js

jQuery Annotated Source.

Home | Previous Chapter | Next Chapter

Effects

var elemdisplay = {},
  iframe, iframeDoc,
  rfxtypes = /^(?:toggle|show|hide)$/,
  rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,
  timerId,
  fxAttrs = [

height animations

    [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],

width animations

    [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],

opacity animations

    [ "opacity" ]
  ],
  fxNow,
  requestAnimationFrame = window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    window.oRequestAnimationFrame;

jQuery.fn.extend({
  show: function( speed, easing, callback ) {
    var elem, display;

    if ( speed || speed === 0 ) {
      return this.animate( genFx("show", 3), speed, easing, callback);

    } else {
      for ( var i = 0, j = this.length; i < j; i++ ) {
        elem = this[i];

        if ( elem.style ) {
          display = elem.style.display;

Reset the inline display of this element to learn if it is being hidden by cascaded rules or not

          if ( !jQuery._data(elem, "olddisplay") && display === "none" ) {
            display = elem.style.display = "";
          }

Set elements which have been overridden with display: none in a stylesheet to whatever the default browser style is for such an element

          if ( display === "" && jQuery.css( elem, "display" ) === "none" ) {
            jQuery._data(elem, "olddisplay", defaultDisplay(elem.nodeName));
          }
        }
      }

Set the display of most of the elements in a second loop to avoid the constant reflow

      for ( i = 0; i < j; i++ ) {
        elem = this[i];

        if ( elem.style ) {
          display = elem.style.display;

          if ( display === "" || display === "none" ) {
            elem.style.display = jQuery._data(elem, "olddisplay") || "";
          }
        }
      }

      return this;
    }
  },

  hide: function( speed, easing, callback ) {
    if ( speed || speed === 0 ) {
      return this.animate( genFx("hide", 3), speed, easing, callback);

    } else {
      for ( var i = 0, j = this.length; i < j; i++ ) {
        if ( this[i].style ) {
          var display = jQuery.css( this[i], "display" );

          if ( display !== "none" && !jQuery._data( this[i], "olddisplay" ) ) {
            jQuery._data( this[i], "olddisplay", display );
          }
        }
      }

Set the display of the elements in a second loop to avoid the constant reflow

      for ( i = 0; i < j; i++ ) {
        if ( this[i].style ) {
          this[i].style.display = "none";
        }
      }

      return this;
    }
  },

Save the old toggle function

  _toggle: jQuery.fn.toggle,

  toggle: function( fn, fn2, callback ) {
    var bool = typeof fn === "boolean";

    if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) {
      this._toggle.apply( this, arguments );

    } else if ( fn == null || bool ) {
      this.each(function() {
        var state = bool ? fn : jQuery(this).is(":hidden");
        jQuery(this)[ state ? "show" : "hide" ]();
      });

    } else {
      this.animate(genFx("toggle", 3), fn, fn2, callback);
    }

    return this;
  },

  fadeTo: function( speed, to, easing, callback ) {
    return this.filter(":hidden").css("opacity", 0).show().end()
          .animate({opacity: to}, speed, easing, callback);
  },

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

    return this[ optall.queue === false ? "each" : "queue" ](function() {

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,
        display, e,
        parts, start, end, unit;

will store per property easing and be used to determine when an animation is complete

      opt.animatedProperties = {};

      for ( p in prop ) {

property name normalization

        name = jQuery.camelCase( p );
        if ( p !== name ) {
          prop[ name ] = prop[ p ];
          delete prop[ p ];
        }

        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" ) {
            if ( !jQuery.support.inlineBlockNeedsLayout ) {
              this.style.display = "inline-block";

            } else {
              display = defaultDisplay( this.nodeName );

inline-level elements accept inline-block; block-level elements need to be inline with layout

              if ( display === "inline" ) {
                this.style.display = "inline-block";

              } else {
                this.style.display = "inline";
                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) ) {
          e[ val === "toggle" ? hidden ? "show" : "hide" : 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;
    });
  },

  stop: function( clearQueue, gotoEnd ) {
    if ( clearQueue ) {
      this.queue([]);
    }

    this.each(function() {
      var timers = jQuery.timers,
        i = timers.length;

clear marker counters if we know they won't be

      if ( !gotoEnd ) {
        jQuery._unmark( true, this );
      }
      while ( i-- ) {
        if ( timers[i].elem === this ) {
          if (gotoEnd) {

force the next step to be the last

            timers[i](true);
          }

          timers.splice(i, 1);
        }
      }
    });

start the next in the queue if the last step wasn't forced

    if ( !gotoEnd ) {
      this.dequeue();
    }

    return this;
  }

});

Animations created synchronously will run synchronously

function createFxNow() {
  setTimeout( clearFxNow, 0 );
  return ( fxNow = jQuery.now() );
}

function clearFxNow() {
  fxNow = undefined;
}

Generate parameters to create a standard animation

function genFx( type, num ) {
  var obj = {};

  jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() {
    obj[ this ] = type;
  });

  return obj;
}

Generate shortcuts for custom animations

jQuery.each({
  slideDown: genFx("show", 1),
  slideUp: genFx("hide", 1),
  slideToggle: genFx("toggle", 1),
  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.extend({
  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
    };

    opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
      opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[opt.duration] : jQuery.fx.speeds._default;

Queueing

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

      if ( opt.queue !== false ) {
        jQuery.dequeue( this );
      } else if ( noUnmark !== false ) {
        jQuery._unmark( this );
      }
    };

    return opt;
  },

  easing: {
    linear: function( p, n, firstNum, diff ) {
      return firstNum + diff * p;
    },
    swing: function( p, n, firstNum, diff ) {
      return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum;
    }
  },

  timers: [],

  fx: function( elem, options, prop ) {
    this.options = options;
    this.elem = elem;
    this.prop = prop;

    options.orig = options.orig || {};
  }

});

jQuery.fx.prototype = {

Simple function for setting a style value

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

    (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );
  },

Get the current size

  cur: function() {
    if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) ) {
      return this.elem[ this.prop ];
    }

    var parsed,
      r = jQuery.css( this.elem, this.prop );

Empty strings, null, undefined and "auto" are converted to 0, complex values such as "rotate(1rad)" are returned as is, simple values such as "10px" are parsed to Float.

    return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed;
  },

Start an animation from one number to another

  custom: function( from, to, unit ) {
    var self = this,
      fx = jQuery.fx,
      raf;

    this.startTime = fxNow || createFxNow();
    this.start = from;
    this.end = to;
    this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" );
    this.now = this.start;
    this.pos = this.state = 0;

    function t( gotoEnd ) {
      return self.step(gotoEnd);
    }

    t.elem = this.elem;

    if ( t() && jQuery.timers.push(t) && !timerId ) {

Use requestAnimationFrame instead of setInterval if available

      if ( requestAnimationFrame ) {
        timerId = true;
        raf = function() {

When timerId gets set to null at any point, this stops

          if ( timerId ) {
            requestAnimationFrame( raf );
            fx.tick();
          }
        };
        requestAnimationFrame( raf );
      } else {
        timerId = setInterval( fx.tick, fx.interval );
      }
    }
  },

Simple 'show' function

  show: function() {

Remember where we started, so that we can go back to it later

    this.options.orig[this.prop] = jQuery.style( this.elem, this.prop );
    this.options.show = true;

Begin the animation Make sure that we start at a small width/height to avoid any flash of content

    this.custom(this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur());

Start by showing the element

    jQuery( this.elem ).show();
  },

Simple 'hide' function

  hide: function() {

Remember where we started, so that we can go back to it later

    this.options.orig[this.prop] = jQuery.style( this.elem, this.prop );
    this.options.hide = true;

Begin the animation

    this.custom(this.cur(), 0);
  },

Each step of an animation

  step: function( gotoEnd ) {
    var t = fxNow || createFxNow(),
      done = true,
      elem = this.elem,
      options = this.options,
      i, n;

    if ( gotoEnd || t >= options.duration + this.startTime ) {
      this.now = this.end;
      this.pos = this.state = 1;
      this.update();

      options.animatedProperties[ this.prop ] = true;

      for ( i in options.animatedProperties ) {
        if ( options.animatedProperties[i] !== true ) {
          done = false;
        }
      }

      if ( done ) {

Reset the overflow

        if ( options.overflow != null && !jQuery.support.shrinkWrapBlocks ) {

          jQuery.each( [ "", "X", "Y" ], function (index, value) {
            elem.style[ "overflow" + value ] = options.overflow[index];
          });
        }

Hide the element if the "hide" operation was done

        if ( options.hide ) {
          jQuery(elem).hide();
        }

Reset the properties, if the item has been hidden or shown

        if ( options.hide || options.show ) {
          for ( var p in options.animatedProperties ) {
            jQuery.style( elem, p, options.orig[p] );
          }
        }

Execute the complete function

        options.complete.call( elem );
      }

      return false;

    } else {

classical easing cannot be used with an Infinity duration

      if ( options.duration == Infinity ) {
        this.now = t;
      } else {
        n = t - this.startTime;
        this.state = n / options.duration;

Perform the easing function, defaults to swing

        this.pos = jQuery.easing[ options.animatedProperties[ this.prop ] ]( this.state, n, 0, 1, options.duration );
        this.now = this.start + ((this.end - this.start) * this.pos);
      }

Perform the next step of the animation

      this.update();
    }

    return true;
  }
};

jQuery.extend( jQuery.fx, {
  tick: function() {
    for ( var timers = jQuery.timers, i = 0 ; i < timers.length ; ++i ) {
      if ( !timers[i]() ) {
        timers.splice(i--, 1);
      }
    }

    if ( !timers.length ) {
      jQuery.fx.stop();
    }
  },

  interval: 13,

  stop: function() {
    clearInterval( timerId );
    timerId = null;
  },

  speeds: {
    slow: 600,
    fast: 200,

Default speed

    _default: 400
  },

  step: {
    opacity: function( fx ) {
      jQuery.style( fx.elem, "opacity", fx.now );
    },

    _default: function( fx ) {
      if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) {
        fx.elem.style[ fx.prop ] = (fx.prop === "width" || fx.prop === "height" ? Math.max(0, fx.now) : fx.now) + fx.unit;
      } else {
        fx.elem[ fx.prop ] = fx.now;
      }
    }
  }
});

if ( jQuery.expr && jQuery.expr.filters ) {
  jQuery.expr.filters.animated = function( elem ) {
    return jQuery.grep(jQuery.timers, function( fn ) {
      return elem === fn.elem;
    }).length;
  };
}

Try to restore the default display value of an element

function defaultDisplay( nodeName ) {

  if ( !elemdisplay[ nodeName ] ) {

    var body = document.body,
      elem = jQuery( "<" + nodeName + ">" ).appendTo( body ),
      display = elem.css( "display" );

    elem.remove();

If the simple way fails, get element's real default display by attaching it to a temp iframe

    if ( display === "none" || display === "" ) {

No iframe to use yet, so create it

      if ( !iframe ) {
        iframe = document.createElement( "iframe" );
        iframe.frameBorder = iframe.width = iframe.height = 0;
      }

      body.appendChild( iframe );

Create a cacheable copy of the iframe document on first call. IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML document to it; WebKit & Firefox won't allow reusing the iframe document.

      if ( !iframeDoc || !iframe.createElement ) {
        iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document;
        iframeDoc.write( ( document.compatMode === "CSS1Compat" ? "<!doctype html>" : "" ) + "<html><body>" );
        iframeDoc.close();
      }

      elem = iframeDoc.createElement( nodeName );

      iframeDoc.body.appendChild( elem );

      display = jQuery.css( elem, "display" );

      body.removeChild( iframe );
    }

Store the correct default display

    elemdisplay[ nodeName ] = display;
  }

  return elemdisplay[ nodeName ];
}