/*! * jquery transit - css3 transitions and transformations * (c) 2011-2014 rico sta. cruz * mit licensed. * * http://ricostacruz.com/jquery.transit * http://github.com/rstacruz/jquery.transit */ /* jshint expr: true */ ;(function (root, factory) { if (typeof define === 'function' && define.amd) { define(['jquery'], factory); } else if (typeof exports === 'object') { module.exports = factory(require('jquery')); } else { factory(root.jquery); } }(this, function($) { $.transit = { version: "0.9.12", // map of $.css() keys to values for 'transitionproperty'. // see https://developer.mozilla.org/en/css/css_transitions#properties_that_can_be_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. // (`transition` => `webkittransition`) function getvendorpropertyname(prop) { // handle unprefixed versions (ff16+, for example) if (prop in div.style) return prop; var prefixes = ['moz', 'webkit', 'o', 'ms']; var prop_ = prop.charat(0).touppercase() + prop.substr(1); for (var i=0; i -1; // check for the browser's transitions support. support.transition = getvendorpropertyname('transition'); support.transitiondelay = getvendorpropertyname('transitiondelay'); support.transform = getvendorpropertyname('transform'); support.transformorigin = getvendorpropertyname('transformorigin'); support.filter = getvendorpropertyname('filter'); support.transform3d = checktransform3dsupport(); var eventnames = { 'transition': 'transitionend', 'moztransition': 'transitionend', 'otransition': 'otransitionend', 'webkittransition': 'webkittransitionend', 'mstransition': 'mstransitionend' }; // detect the 'transitionend' event needed. var transitionend = support.transitionend = eventnames[support.transition] || null; // populate jquery's `$.support` with the vendor prefixes we know. // as per [jquery's csshooks documentation](http://api.jquery.com/jquery.csshooks/), // we set $.support.transition to a string of the actual property name used. for (var key in support) { if (support.hasownproperty(key) && typeof $.support[key] === 'undefined') { $.support[key] = support[key]; } } // avoid memory leak in ie. div = null; // ## $.cssease // list of easing aliases that you can use with `$.fn.transition`. $.cssease = { '_default': 'ease', 'in': 'ease-in', 'out': 'ease-out', 'in-out': 'ease-in-out', 'snap': 'cubic-bezier(0,1,.5,1)', // penner equations 'easeincubic': 'cubic-bezier(.550,.055,.675,.190)', 'easeoutcubic': 'cubic-bezier(.215,.61,.355,1)', 'easeinoutcubic': 'cubic-bezier(.645,.045,.355,1)', 'easeincirc': 'cubic-bezier(.6,.04,.98,.335)', 'easeoutcirc': 'cubic-bezier(.075,.82,.165,1)', 'easeinoutcirc': 'cubic-bezier(.785,.135,.15,.86)', 'easeinexpo': 'cubic-bezier(.95,.05,.795,.035)', 'easeoutexpo': 'cubic-bezier(.19,1,.22,1)', 'easeinoutexpo': 'cubic-bezier(1,0,0,1)', 'easeinquad': 'cubic-bezier(.55,.085,.68,.53)', 'easeoutquad': 'cubic-bezier(.25,.46,.45,.94)', 'easeinoutquad': 'cubic-bezier(.455,.03,.515,.955)', 'easeinquart': 'cubic-bezier(.895,.03,.685,.22)', 'easeoutquart': 'cubic-bezier(.165,.84,.44,1)', 'easeinoutquart': 'cubic-bezier(.77,0,.175,1)', 'easeinquint': 'cubic-bezier(.755,.05,.855,.06)', 'easeoutquint': 'cubic-bezier(.23,1,.32,1)', 'easeinoutquint': 'cubic-bezier(.86,0,.07,1)', 'easeinsine': 'cubic-bezier(.47,0,.745,.715)', 'easeoutsine': 'cubic-bezier(.39,.575,.565,1)', 'easeinoutsine': 'cubic-bezier(.445,.05,.55,.95)', 'easeinback': 'cubic-bezier(.6,-.28,.735,.045)', 'easeoutback': 'cubic-bezier(.175, .885,.32,1.275)', 'easeinoutback': 'cubic-bezier(.68,-.55,.265,1.55)' }; // ## 'transform' css hook // allows you to use the `transform` property in css. // // $("#hello").css({ transform: "rotate(90deg)" }); // // $("#hello").css('transform'); // //=> { rotate: '90deg' } // $.csshooks['transit:transform'] = { // the getter returns a `transform` object. get: function(elem) { return $(elem).data('transform') || new transform(); }, // the setter accepts a `transform` object or a string. 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); } }; // add a css hook for `.css({ transform: '...' })`. // in jquery 1.8+, this will intentionally override the default `transform` // css hook so it'll play well with transit. (see issue #62) $.csshooks.transform = { set: $.csshooks['transit:transform'].set }; // ## 'filter' css hook // allows you to use the `filter` property in css. // // $("#hello").css({ filter: 'blur(10px)' }); // $.csshooks.filter = { get: function(elem) { return elem.style[support.filter]; }, set: function(elem, value) { elem.style[support.filter] = value; } }; // jquery 1.8+ supports prefix-free transitions, so these polyfills will not // be necessary. if ($.fn.jquery < "1.8") { // ## 'transformorigin' css hook // allows the use for `transformorigin` to define where scaling and rotation // is pivoted. // // $("#hello").css({ transformorigin: '0 0' }); // $.csshooks.transformorigin = { get: function(elem) { return elem.style[support.transformorigin]; }, set: function(elem, value) { elem.style[support.transformorigin] = value; } }; // ## 'transition' css hook // allows you to use the `transition` property in css. // // $("#hello").css({ transition: 'all 0 ease 0' }); // $.csshooks.transition = { get: function(elem) { return elem.style[support.transition]; }, set: function(elem, value) { elem.style[support.transition] = value; } }; } // ## other css hooks // allows you to rotate, scale and translate. registercsshook('scale'); registercsshook('scalex'); registercsshook('scaley'); registercsshook('translate'); registercsshook('rotate'); registercsshook('rotatex'); registercsshook('rotatey'); registercsshook('rotate3d'); registercsshook('perspective'); registercsshook('skewx'); registercsshook('skewy'); registercsshook('x', true); registercsshook('y', true); // ## transform class // this is the main class of a transformation property that powers // `$.fn.css({ transform: '...' })`. // // this is, in essence, a dictionary object with key/values as `-transform` // properties. // // var t = new transform("rotate(90) scale(4)"); // // t.rotate //=> "90deg" // t.scale //=> "4,4" // // setters are accounted for. // // t.set('rotate', 4) // t.rotate //=> "4deg" // // convert it to a css string using the `tostring()` and `tostring(true)` (for webkit) // functions. // // t.tostring() //=> "rotate(90deg) scale(4,4)" // t.tostring(true) //=> "rotate(90deg) scale3d(4,4,0)" (webkit version) // function transform(str) { if (typeof str === 'string') { this.parse(str); } return this; } transform.prototype = { // ### setfromstring() // sets a property from a string. // // t.setfromstring('scale', '2,4'); // // same as set('scale', '2', '4'); // 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. // // t.set('scale', 2, 4); // 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 // // .css({ rotate: 30 }) // .css({ rotate: "30" }) // .css({ rotate: "30deg" }) // .css({ rotate: "30deg" }) // 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 // // .css({ scale: 9 }) //=> "scale(9,9)" // .css({ scale: '3,2' }) //=> "scale(3,2)" // 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 / y // translations. notice how this keeps the other value. // // .css({ x: 4 }) //=> "translate(4px, 0)" // .css({ y: 10 }) //=> "translate(4px, 10px)" // x: function(x) { this.set('translate', x, null); }, y: function(y) { this.set('translate', null, y); }, // ### translate // notice how this keeps the other value. // // .css({ translate: '2, 5' }) //=> "translate(2px, 5px)" // translate: function(x, y) { if (this._translatex === undefined) { this._translatex = 0; } if (this._translatey === undefined) { this._translatey = 0; } if (x !== null && x !== undefined) { this._translatex = unit(x, 'px'); } if (y !== null && y !== undefined) { 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 `transition` css property string. if `use3d` is given, // it converts to a `-webkit-transition` css property string instead. 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 { self.each(function () { fn.call(this); }); } } // ### getproperties(dict) // returns properties (for `transition-property`) for dictionary `props`. the // value of `props` is what you would expect in `$.css(...)`. function getproperties(props) { var re = []; $.each(props, function(key) { key = $.camelcase(key); // convert "text-align" => "textalign" key = $.transit.propertymap[key] || $.cssprops[key] || key; key = uncamel(key); // convert back to dasherized // get vendor specify propertie if (support[key]) key = uncamel(support[key]); if ($.inarray(key, re) === -1) { re.push(key); } }); return re; } // ### gettransition() // returns the transition string to be used for the `transition` css property. // // example: // // gettransition({ opacity: 1, rotate: 30 }, 500, 'ease'); // //=> 'opacity 500ms ease, -webkit-transform 500ms ease' // function gettransition(properties, duration, easing, delay) { // get the css properties needed. var props = getproperties(properties); // account for aliases (`in` => `ease-in`). 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.transition // works like $.fn.animate(), but uses css transitions. // // $("...").transition({ opacity: 0.1, scale: 0.3 }); // // // specific duration // $("...").transition({ opacity: 0.1, scale: 0.3 }, 500); // // // with duration and easing // $("...").transition({ opacity: 0.1, scale: 0.3 }, 500, 'in'); // // // with callback // $("...").transition({ opacity: 0.1, scale: 0.3 }, function() { ... }); // // // with everything // $("...").transition({ opacity: 0.1, scale: 0.3 }, 500, 'in', function() { ... }); // // // alternate syntax // $("...").transition({ // opacity: 0.1, // duration: 200, // delay: 40, // easing: 'in', // complete: function() { /* ... */ } // }); // $.fn.transition = $.fn.transit = function(properties, duration, easing, callback) { var self = this; var delay = 0; var queue = true; var theseproperties = $.extend(true, {}, properties); // account for `.transition(properties, callback)`. if (typeof duration === 'function') { callback = duration; duration = undefined; } // account for `.transition(properties, options)`. if (typeof duration === 'object') { easing = duration.easing; delay = duration.delay || 0; queue = typeof duration.queue === "undefined" ? true : duration.queue; callback = duration.complete; duration = duration.duration; } // account for `.transition(properties, duration, callback)`. if (typeof easing === 'function') { callback = easing; easing = undefined; } // alternate syntax. if (typeof theseproperties.easing !== 'undefined') { easing = theseproperties.easing; delete theseproperties.easing; } if (typeof theseproperties.duration !== 'undefined') { duration = theseproperties.duration; delete theseproperties.duration; } if (typeof theseproperties.complete !== 'undefined') { callback = theseproperties.complete; delete theseproperties.complete; } if (typeof theseproperties.queue !== 'undefined') { queue = theseproperties.queue; delete theseproperties.queue; } if (typeof theseproperties.delay !== 'undefined') { delay = theseproperties.delay; delete theseproperties.delay; } // set defaults. (`400` duration, `ease` easing) if (typeof duration === 'undefined') { duration = $.fx.speeds._default; } if (typeof easing === 'undefined') { easing = $.cssease._default; } duration = toms(duration); // build the `transition` property. var transitionvalue = gettransition(theseproperties, 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(theseproperties); if (callback) { callback.apply(self); } if (next) { 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(theseproperties); }); }; // defer running. this allows the browser to paint any pending css it hasn't // painted yet before doing the transitions. var deferredrun = function(next) { this.offsetwidth; // force a repaint run(next); }; // 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('transit:transform'); return t.get(prop); }, set: function(elem, value) { var t = $(elem).css('transit:transform'); t.setfromstring(prop, value); $(elem).css({ 'transit:transform': t }); } }; } // ### uncamel(str) // converts a camelcase string to a dasherized string. // (`marginleft` => `margin-left`) function uncamel(str) { return str.replace(/([a-z])/g, function(letter) { return '-' + letter.tolowercase(); }); } // ### unit(number, unit) // ensures that number `number` has a unit. if no unit is found, assume the // default is `unit`. // // unit(2, 'px') //=> "2px" // unit("30deg", 'rad') //=> "30deg" // function unit(i, units) { if ((typeof i === "string") && (!i.match(/^[\-0-9\.]+$/))) { return i; } else { return "" + i + units; } } // ### toms(duration) // converts given `duration` to a millisecond string. // // toms('fast') => $.fx.speeds[i] => "200ms" // toms('normal') //=> $.fx.speeds._default => "400ms" // toms(10) //=> '10ms' // toms('100ms') //=> '100ms' // function toms(duration) { var i = duration; // allow string durations like 'fast' and 'slow', without overriding numeric values. if (typeof i === 'string' && (!i.match(/^[\-0-9\.]+/))) { i = $.fx.speeds[i] || $.fx.speeds._default; } return unit(i, 'ms'); } // export some functions for testable-ness. $.transit.gettransitionvalue = gettransition; return $; }));