jquery.transit.js | |
|---|---|
/*!
* jQuery Transit - CSS3 transitions and transformations
* Copyright(c) 2011 Rico Sta. Cruz <rico@ricostacruz.com>
* MIT Licensed.
*
* http://ricostacruz.com/jquery.transit
* http://github.com/rstacruz/jquery.transit
*/
(function($) {
"use strict";
$.transit = {
version: "0.1.3", | |
| Map of $.css() keys to values for 'transitionProperty'. See https://developer.mozilla.org/en/CSS/CSStransitions#Propertiesthatcanbe_animated | propertyMap: {
marginLeft : 'margin',
marginRight : 'margin',
marginBottom : 'margin',
marginTop : 'margin',
paddingLeft : 'padding',
paddingRight : 'padding',
paddingBottom : 'padding',
paddingTop : 'padding'
}, |
| Will simply transition "instantly" if false | enabled: true, |
| Set this to false if you don't want to use the transition end property. | useTransitionEnd: false
};
var div = document.createElement('div');
var support = {}; |
| Helper function to get the proper vendor property name.
( | function getVendorPropertyName(prop) {
var prefixes = ['Moz', 'Webkit', 'O', 'ms'];
var prop_ = prop.charAt(0).toUpperCase() + prop.substr(1);
if (prop in div.style) { return prop; }
for (var i=0; i<prefixes.length; ++i) {
var vendorProp = prefixes[i] + prop_;
if (vendorProp in div.style) { return vendorProp; }
}
} |
| Helper function to check if transform3D is supported. Should return true for Webkits and Firefox 10+. | function checkTransform3dSupport() {
div.style[support.transform] = '';
div.style[support.transform] = 'rotateY(90deg)';
return div.style[support.transform] !== '';
}
var isChrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1; |
| Check for the browser's transitions support.
You can access this in jQuery's | support.transition = getVendorPropertyName('transition');
support.transitionDelay = getVendorPropertyName('transitionDelay');
support.transform = getVendorPropertyName('transform');
support.transformOrigin = getVendorPropertyName('transformOrigin');
support.transform3d = checkTransform3dSupport();
$.extend($.support, support);
var eventNames = {
'MozTransition': 'transitionend',
'OTransition': 'oTransitionEnd',
'WebkitTransition': 'webkitTransitionEnd',
'msTransition': 'MSTransitionEnd'
}; |
| Detect the 'transitionend' event needed. | var transitionEnd = support.transitionEnd = eventNames[support.transition] || null; |
| Avoid memory leak in IE. | div = null; |
$.cssEaseList of easing aliases that you can use with | $.cssEase = {
'_default': 'ease',
'in': 'ease-in',
'out': 'ease-out',
'in-out': 'ease-in-out',
'snap': 'cubic-bezier(0,1,.5,1)'
}; |
'transform' CSS hookAllows you to use the | $.cssHooks.transform = { |
| The getter returns a | get: function(elem) {
return $(elem).data('transform');
}, |
| The setter accepts a | set: function(elem, v) {
var value = v;
if (!(value instanceof Transform)) {
value = new Transform(value);
} |
| We've seen the 3D version of Scale() not work in Chrome when the element being scaled extends outside of the viewport. Thus, we're forcing Chrome to not use the 3d transforms as well. Not sure if translate is affectede, but not risking it. Detection code from http://davidwalsh.name/detecting-google-chrome-javascript | if (support.transform === 'WebkitTransform' && !isChrome) {
elem.style[support.transform] = value.toString(true);
} else {
elem.style[support.transform] = value.toString();
}
$(elem).data('transform', value);
}
}; |
'transformOrigin' CSS hookAllows the use for | $.cssHooks.transformOrigin = {
get: function(elem) {
return elem.style[support.transformOrigin];
},
set: function(elem, value) {
elem.style[support.transformOrigin] = value;
}
}; |
Other CSS hooksAllows you to rotate, scale and translate. | registerCssHook('scale');
registerCssHook('translate');
registerCssHook('rotate');
registerCssHook('rotateX');
registerCssHook('rotateY');
registerCssHook('rotate3d');
registerCssHook('perspective');
registerCssHook('skewX');
registerCssHook('skewY');
registerCssHook('x', true);
registerCssHook('y', true); |
Transform classThis is the main class of a transformation property that powers
This is, in essence, a dictionary object with key/values as
Setters are accounted for.
Convert it to a CSS string using the | function Transform(str) {
if (typeof str === 'string') { this.parse(str); }
return this;
}
Transform.prototype = { |
setFromString()Sets a property from a string. | setFromString: function(prop, val) {
var args =
(typeof val === 'string') ? val.split(',') :
(val.constructor === Array) ? val :
[ val ];
args.unshift(prop);
Transform.prototype.set.apply(this, args);
}, |
set()Sets a property. | set: function(prop) {
var args = Array.prototype.slice.apply(arguments, [1]);
if (this.setter[prop]) {
this.setter[prop].apply(this, args);
} else {
this[prop] = args.join(',');
}
},
get: function(prop) {
if (this.getter[prop]) {
return this.getter[prop].apply(this);
} else {
return this[prop] || 0;
}
},
setter: { |
rotate | rotate: function(theta) {
this.rotate = unit(theta, 'deg');
},
rotateX: function(theta) {
this.rotateX = unit(theta, 'deg');
},
rotateY: function(theta) {
this.rotateY = unit(theta, 'deg');
}, |
scale | scale: function(x, y) {
if (y === undefined) { y = x; }
this.scale = x + "," + y;
}, |
skewX + skewY | skewX: function(x) {
this.skewX = unit(x, 'deg');
},
skewY: function(y) {
this.skewY = unit(y, 'deg');
}, |
perspectvie | perspective: function(dist) {
this.perspective = unit(dist, 'px');
}, |
x / yTranslations. Notice how this keeps the other value. | x: function(x) {
this.set('translate', x, null);
},
y: function(y) {
this.set('translate', null, y);
}, |
translateNotice how this keeps the other value. | translate: function(x, y) {
if (this._translateX === undefined) { this._translateX = 0; }
if (this._translateY === undefined) { this._translateY = 0; }
if (x !== null) { this._translateX = unit(x, 'px'); }
if (y !== null) { this._translateY = unit(y, 'px'); }
this.translate = this._translateX + "," + this._translateY;
}
},
getter: {
x: function() {
return this._translateX || 0;
},
y: function() {
return this._translateY || 0;
},
scale: function() {
var s = (this.scale || "1,1").split(',');
if (s[0]) { s[0] = parseFloat(s[0]); }
if (s[1]) { s[1] = parseFloat(s[1]); } |
| "2.5,2.5" => 2.5 "2.5,1" => [2.5,1] | return (s[0] === s[1]) ? s[0] : s;
},
rotate3d: function() {
var s = (this.rotate3d || "0,0,0,0deg").split(',');
for (var i=0; i<=3; ++i) {
if (s[i]) { s[i] = parseFloat(s[i]); }
}
if (s[3]) { s[3] = unit(s[3], 'deg'); }
return s;
}
}, |
parse()Parses from a string. Called on constructor. | parse: function(str) {
var self = this;
str.replace(/([a-zA-Z0-9]+)\((.*?)\)/g, function(x, prop, val) {
self.setFromString(prop, val);
});
}, |
toString()Converts to a | toString: function(use3d) {
var re = [];
for (var i in this) {
if (this.hasOwnProperty(i)) { |
| Don't use 3D transformations if the browser can't support it. | if ((!support.transform3d) && (
(i === 'rotateX') ||
(i === 'rotateY') ||
(i === 'perspective') ||
(i === 'transformOrigin'))) { continue; }
if (i[0] !== '_') {
if (use3d && (i === 'scale')) {
re.push(i + "3d(" + this[i] + ",1)");
} else if (use3d && (i === 'translate')) {
re.push(i + "3d(" + this[i] + ",0)");
} else {
re.push(i + "(" + this[i] + ")");
}
}
}
}
return re.join(" ");
}
};
function callOrQueue(self, queue, fn) {
if (queue === true) {
self.queue(fn);
} else if (queue) {
self.queue(queue, fn);
} else {
fn();
}
} |
getProperties(dict)Returns properties (for | function getProperties(props) {
var re = [];
$.each(props, function(key) {
key = $.camelCase(key); // Convert "text-align" => "textAlign"
key = $.transit.propertyMap[key] || key;
key = uncamel(key); // Convert back to dasherized
if ($.inArray(key, re) === -1) { re.push(key); }
});
return re;
} |
getTransition()Returns the transition string to be used for the Example: | function getTransition(properties, duration, easing, delay) { |
| Get the CSS properties needed. | var props = getProperties(properties); |
| Account for aliases ( | if ($.cssEase[easing]) { easing = $.cssEase[easing]; } |
| Build the duration/easing/delay attributes for it. | var attribs = '' + toMS(duration) + ' ' + easing;
if (parseInt(delay, 10) > 0) { attribs += ' ' + toMS(delay); } |
| For more properties, add them this way: "margin 200ms ease, padding 200ms ease, ..." | var transitions = [];
$.each(props, function(i, name) {
transitions.push(name + ' ' + attribs);
});
return transitions.join(', ');
} |
$.fn.transitionWorks like $.fn.animate(), but uses CSS transitions. | $.fn.transition = $.fn.transit = function(properties, duration, easing, callback) {
var self = this;
var delay = 0;
var queue = true; |
| Account for | if (typeof duration === 'function') {
callback = duration;
duration = undefined;
} |
| Account for | if (typeof easing === 'function') {
callback = easing;
easing = undefined;
} |
| Alternate syntax. | if (typeof properties.easing !== 'undefined') {
easing = properties.easing;
delete properties.easing;
}
if (typeof properties.duration !== 'undefined') {
duration = properties.duration;
delete properties.duration;
}
if (typeof properties.complete !== 'undefined') {
callback = properties.complete;
delete properties.complete;
}
if (typeof properties.queue !== 'undefined') {
queue = properties.queue;
delete properties.queue;
}
if (typeof properties.delay !== 'undefined') {
delay = properties.delay;
delete properties.delay;
} |
| Set defaults. ( | if (typeof duration === 'undefined') { duration = $.fx.speeds._default; }
if (typeof easing === 'undefined') { easing = $.cssEase._default; }
duration = toMS(duration); |
| Build the | var transitionValue = getTransition(properties, duration, easing, delay); |
| Compute delay until callback. If this becomes 0, don't bother setting the transition property. | var work = $.transit.enabled && support.transition;
var i = work ? (parseInt(duration, 10) + parseInt(delay, 10)) : 0; |
| If there's nothing to do... | if (i === 0) {
var fn = function(next) {
self.css(properties);
if (callback) { callback(); }
next();
};
callOrQueue(self, queue, fn);
return self;
} |
| Save the old transitions of each element so we can restore it later. | var oldTransitions = {};
var run = function(nextCall) {
var bound = false; |
| Prepare the callback. | var cb = function() {
if (bound) { self.unbind(transitionEnd, cb); }
if (i > 0) {
self.each(function() {
this.style[support.transition] = (oldTransitions[this] || null);
});
}
if (typeof callback === 'function') { callback.apply(self); }
if (typeof nextCall === 'function') { nextCall(); }
};
if ((i > 0) && (transitionEnd) && ($.transit.useTransitionEnd)) { |
| Use the 'transitionend' event if it's available. | bound = true;
self.bind(transitionEnd, cb);
} else { |
| Fallback to timers if the 'transitionend' event isn't supported. | window.setTimeout(cb, i);
} |
| Apply transitions. | self.each(function() {
if (i > 0) {
this.style[support.transition] = transitionValue;
}
$(this).css(properties);
});
}; |
| Defer running. This allows the browser to paint any pending CSS it hasn't painted yet before doing the transitions. | var deferredRun = function(next) {
var i = 0; |
| Durations that are too slow will get transitions mixed up. (Tested on Mac/FF 7.0.1) | if ((support.transition === 'MozTransition') && (i < 25)) { i = 25; }
window.setTimeout(function() { run(next); }, i);
}; |
| Use jQuery's fx queue. | callOrQueue(self, queue, deferredRun); |
| Chainability. | return this;
};
function registerCssHook(prop, isPixels) { |
| For certain properties, the 'px' should not be implied. | if (!isPixels) { $.cssNumber[prop] = true; }
$.transit.propertyMap[prop] = support.transform;
$.cssHooks[prop] = {
get: function(elem) {
var t = $(elem).css('transform') || new Transform();
return t.get(prop);
},
set: function(elem, value) {
var t = $(elem).css('transform') || new Transform();
t.setFromString(prop, value);
$(elem).css({ transform: t });
}
};
} |
uncamel(str)Converts a camelcase string to a dasherized string.
( | function uncamel(str) {
return str.replace(/([A-Z])/g, function(letter) { return '-' + letter.toLowerCase(); });
} |
unit(number, unit)Ensures that number | function unit(i, units) {
if ((typeof i === "string") && (!i.match(/^[\-0-9\.]+$/))) {
return i;
} else {
return "" + i + units;
}
} |
toMS(duration)Converts given | function toMS(duration) {
var i = duration; |
| Allow for string durations like 'fast'. | if ($.fx.speeds[i]) { i = $.fx.speeds[i]; }
return unit(i, 'ms');
} |
| Export some functions for testable-ness. | $.transit.getTransitionValue = getTransition;
})(jQuery);
|