Jump To …

animate.js

(function ($) {

Overwrites jQuery.fn.animate to use CSS 3 animations if possible

  var

The global animation counter

  animationNum = 0,

The stylesheet for our animations

    styleSheet = null,

The animation cache

    cache = [],

Stores the browser properties like transition end event name and prefix

    browser = null,

Store the original $.fn.animate

    oldanimate = $.fn.animate,

Return the stylesheet, create it if it doesn't exists

    getStyleSheet = function () {
      if (!styleSheet) {
        var style = document.createElement('style');
        style.setAttribute("type", "text/css");
        style.setAttribute("media", "screen");

        document.getElementsByTagName('head')[0].appendChild(style);
        if (!window.createPopup) {
          style.appendChild(document.createTextNode(''));
        }

        styleSheet = style.sheet;
      }

      return styleSheet;
    },

removes an animation rule from a sheet

    removeAnimation = function (sheet, name) {
      for (var j = sheet.cssRules.length - 1; j >= 0; j--) {
        var rule = sheet.cssRules[j];

7 means the keyframe rule

        if (rule.type === 7 && rule.name == name) {
          sheet.deleteRule(j)
          return;
        }
      }
    },

Returns whether the animation should be passed to the original $.fn.animate.

    passThrough = function (props, ops) {
      var nonElement = !(this[0] && this[0].nodeType),
        isInline = !nonElement && $(this).css("display") === "inline" && $(this).css("float") === "none";

      for (var name in props) {

jQuery does something with these values

        if (props[name] == 'show' || props[name] == 'hide' || props[name] == 'toggle'

Arrays for individual easing

        || $.isArray(props[name])

Negative values not handled the same

        || props[name] < 0

unit-less value

        || name == 'zIndex' || name == 'z-index' || name == 'scrollTop' || name == 'scrollLeft') {
          return true;
        }
      }

      return props.jquery === true || getBrowser() === null ||

Animating empty properties

      $.isEmptyObject(props) ||

We can't do custom easing

      (ops && ops.length == 4) || (ops && typeof ops[2] == 'string') ||

Second parameter is an object - we can only handle primitives

      $.isPlainObject(ops) ||

Inline and non elements

      isInline || nonElement;
    },

Gets a CSS number (with px added as the default unit if the value is a number)

    cssValue = function (origName, value) {
      if (typeof value === "number" && !$.cssNumber[origName]) {
        return value += "px";
      }
      return value;
    },

Feature detection borrowed by http://modernizr.com/

    getBrowser = function () {
      if (!browser) {
        var t, el = document.createElement('fakeelement'),
          transitions = {
            'transition': {
              transitionEnd: 'transitionEnd',
              prefix: ''
            },
                'OTransition': {
                    transitionEnd : 'oTransitionEnd',
                    prefix : '-o-'
                },
                'MSTransition': {
                    transitionEnd : 'msTransitionEnd',
                    prefix : '-ms-'
                },
            'MozTransition': {
              transitionEnd: 'animationend',
              prefix: '-moz-'
            },
            'WebkitTransition': {
              transitionEnd: 'webkitAnimationEnd',
              prefix: '-webkit-'
            }
          }

          for (t in transitions) {
            if (el.style[t] !== undefined) {
              browser = transitions[t];
            }
          }
      }
      return browser;
    },

Properties that Firefox can't animate if set to 'auto': https://bugzilla.mozilla.org/show_bug.cgi?id=571344 Provides a converter that returns the actual value

    ffProps = {
      top: function (el) {
        return el.position().top;
      },
      left: function (el) {
        return el.position().left;
      },
      width: function (el) {
        return el.width();
      },
      height: function (el) {
        return el.height();
      },
      fontSize: function (el) {
        return '1em';
      }
    },

Add browser specific prefix

    addPrefix = function (properties) {
      var result = {};
      $.each(properties, function (name, value) {
        result[getBrowser().prefix + name] = value;
      });
      return result;
    },

Returns the animation name for a given style. It either uses a cached version or adds it to the stylesheet, removing the oldest style if the cache has reached a certain size.

    getAnimation = function (style) {
      var sheet, name, last;

Look up the cached style, set it to that name and reset age if found increment the age for any other animation

      $.each(cache, function (i, animation) {
        if (style === animation.style) {
          name = animation.name;
          animation.age = 0;
        } else {
          animation.age += 1;
        }
      });

      if (!name) { // Add a new style
        sheet = getStyleSheet();
        name = "jquerypp_animation_" + (animationNum++);

get the last sheet and insert this rule into it

        sheet.insertRule("@" + getBrowser().prefix + "keyframes " + name + ' ' + style, (sheet.cssRules && sheet.cssRules.length) || 0);
        cache.push({
          name: name,
          style: style,
          age: 0
        });

Sort the cache by age

        cache.sort(function (first, second) {
          return first.age - second.age;
        });

Remove the last (oldest) item from the cache if it has more than 20 items

        if (cache.length > 20) {
          last = cache.pop();
          removeAnimation(sheet, last.name);
        }
      }

      return name;
    };


  $.fn.animate = function (props, speed, easing, callback) {

default to normal animations if browser doesn't support them

    if (passThrough.apply(this, arguments)) {
      return oldanimate.apply(this, arguments);
    }

    var optall = $.speed(speed, easing, callback);

Add everything to the animation queue

    this.queue(optall.queue, function (done) {
      var

current CSS values

      current,

The list of properties passed

      properties = [],
        to = "",
        prop, self = $(this),
        duration = optall.duration,

the animation keyframe name

        animationName,

The key used to store the animation hook

        dataKey,

the text for the keyframe

        style = "{ from {",

The animation end event handler. Will be called both on animation end and after calling .stop()

        animationEnd = function (currentCSS, exec) {
          self.css(currentCSS);

          self.css(addPrefix({
            "animation-duration": "",
            "animation-name": "",
            "animation-fill-mode": "",
            "animation-play-state": ""
          }));

Call the original callback

          if ($.isFunction(optall.old) && exec) {

Call success, pass the DOM element as the this reference

            optall.old.call(self[0], true)
          }

          $.removeData(self, dataKey, true);
        },
        finishAnimation = function () {

Call animationEnd using the passed properties

          animationEnd(props, true);
          done();
        };

      for (prop in props) {
        properties.push(prop);
      }

      if (getBrowser().prefix === '-moz-') {

Normalize 'auto' properties in FF

        $.each(properties, function (i, prop) {
          var converter = ffProps[$.camelCase(prop)];
          if (converter && self.css(prop) == 'auto') {
            self.css(prop, converter(self));
          }
        });
      }

Use $.styles

      current = self.styles.apply(self, properties);
      $.each(properties, function (i, cur) {

Convert a camelcased property name

        var name = cur.replace(/([A-Z]|^ms)/g, "-$1").toLowerCase();
        style += name + " : " + cssValue(cur, current[cur]) + "; ";
        to += name + " : " + cssValue(cur, props[cur]) + "; ";
      });

      style += "} to {" + to + " }}";

      animationName = getAnimation(style);
      dataKey = animationName + '.run';

Add a hook which will be called when the animation stops

      $._data(this, dataKey, {
        stop: function (gotoEnd) {

Pause the animation

          self.css(addPrefix({
            'animation-play-state': 'paused'
          }));

Unbind the animation end handler

          self.off(getBrowser().transitionEnd, finishAnimation);
          if (!gotoEnd) {

We were told not to finish the animation Call animationEnd but set the CSS to the current computed style

            animationEnd(self.styles.apply(self, properties), false);
          } else {

Finish animaion

            animationEnd(props, true);
          }
        }
      });

set this element to point to that animation

      self.css(addPrefix({
        "animation-duration": duration + "ms",
        "animation-name": animationName,
        "animation-fill-mode": "forwards"
      }));

Attach the transition end event handler to run only once

      self.one(getBrowser().transitionEnd, finishAnimation);

    });

    return this;
  };

  return $;
})(jQuery);