animate.js | |
---|---|
(function ($) { | |
Overwrites | 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: ''
}, |
| '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);
|