jquery.transit.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  1. /*!
  2. * jQuery Transit - CSS3 transitions and transformations
  3. * Copyright(c) 2011 Rico Sta. Cruz <rico@ricostacruz.com>
  4. * MIT Licensed.
  5. *
  6. * http://ricostacruz.com/jquery.transit
  7. * http://github.com/rstacruz/jquery.transit
  8. */
  9. (function($) {
  10. "use strict";
  11. $.transit = {
  12. version: "0.1.3",
  13. // Map of $.css() keys to values for 'transitionProperty'.
  14. // See https://developer.mozilla.org/en/CSS/CSS_transitions#Properties_that_can_be_animated
  15. propertyMap: {
  16. marginLeft : 'margin',
  17. marginRight : 'margin',
  18. marginBottom : 'margin',
  19. marginTop : 'margin',
  20. paddingLeft : 'padding',
  21. paddingRight : 'padding',
  22. paddingBottom : 'padding',
  23. paddingTop : 'padding'
  24. },
  25. // Will simply transition "instantly" if false
  26. enabled: true,
  27. // Set this to false if you don't want to use the transition end property.
  28. useTransitionEnd: false
  29. };
  30. var div = document.createElement('div');
  31. var support = {};
  32. // Helper function to get the proper vendor property name.
  33. // (`transition` => `WebkitTransition`)
  34. function getVendorPropertyName(prop) {
  35. var prefixes = ['Moz', 'Webkit', 'O', 'ms'];
  36. var prop_ = prop.charAt(0).toUpperCase() + prop.substr(1);
  37. if (prop in div.style) { return prop; }
  38. for (var i=0; i<prefixes.length; ++i) {
  39. var vendorProp = prefixes[i] + prop_;
  40. if (vendorProp in div.style) { return vendorProp; }
  41. }
  42. }
  43. // Helper function to check if transform3D is supported.
  44. // Should return true for Webkits and Firefox 10+.
  45. function checkTransform3dSupport() {
  46. div.style[support.transform] = '';
  47. div.style[support.transform] = 'rotateY(90deg)';
  48. return div.style[support.transform] !== '';
  49. }
  50. var isChrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1;
  51. // Check for the browser's transitions support.
  52. // You can access this in jQuery's `$.support.transition`.
  53. // As per [jQuery's cssHooks documentation](http://api.jquery.com/jQuery.cssHooks/),
  54. // we set $.support.transition to a string of the actual property name used.
  55. support.transition = getVendorPropertyName('transition');
  56. support.transitionDelay = getVendorPropertyName('transitionDelay');
  57. support.transform = getVendorPropertyName('transform');
  58. support.transformOrigin = getVendorPropertyName('transformOrigin');
  59. support.transform3d = checkTransform3dSupport();
  60. $.extend($.support, support);
  61. var eventNames = {
  62. 'MozTransition': 'transitionend',
  63. 'OTransition': 'oTransitionEnd',
  64. 'WebkitTransition': 'webkitTransitionEnd',
  65. 'msTransition': 'MSTransitionEnd'
  66. };
  67. // Detect the 'transitionend' event needed.
  68. var transitionEnd = support.transitionEnd = eventNames[support.transition] || null;
  69. // Avoid memory leak in IE.
  70. div = null;
  71. // ## $.cssEase
  72. // List of easing aliases that you can use with `$.fn.transition`.
  73. $.cssEase = {
  74. '_default': 'ease',
  75. 'in': 'ease-in',
  76. 'out': 'ease-out',
  77. 'in-out': 'ease-in-out',
  78. 'snap': 'cubic-bezier(0,1,.5,1)'
  79. };
  80. // ## 'transform' CSS hook
  81. // Allows you to use the `transform` property in CSS.
  82. //
  83. // $("#hello").css({ transform: "rotate(90deg)" });
  84. //
  85. // $("#hello").css('transform');
  86. // //=> { rotate: '90deg' }
  87. //
  88. $.cssHooks.transform = {
  89. // The getter returns a `Transform` object.
  90. get: function(elem) {
  91. return $(elem).data('transform');
  92. },
  93. // The setter accepts a `Transform` object or a string.
  94. set: function(elem, v) {
  95. var value = v;
  96. if (!(value instanceof Transform)) {
  97. value = new Transform(value);
  98. }
  99. // We've seen the 3D version of Scale() not work in Chrome when the
  100. // element being scaled extends outside of the viewport. Thus, we're
  101. // forcing Chrome to not use the 3d transforms as well. Not sure if
  102. // translate is affectede, but not risking it. Detection code from
  103. // http://davidwalsh.name/detecting-google-chrome-javascript
  104. if (support.transform === 'WebkitTransform' && !isChrome) {
  105. elem.style[support.transform] = value.toString(true);
  106. } else {
  107. elem.style[support.transform] = value.toString();
  108. }
  109. $(elem).data('transform', value);
  110. }
  111. };
  112. // ## 'transformOrigin' CSS hook
  113. // Allows the use for `transformOrigin` to define where scaling and rotation
  114. // is pivoted.
  115. //
  116. // $("#hello").css({ transformOrigin: '0 0' });
  117. //
  118. $.cssHooks.transformOrigin = {
  119. get: function(elem) {
  120. return elem.style[support.transformOrigin];
  121. },
  122. set: function(elem, value) {
  123. elem.style[support.transformOrigin] = value;
  124. }
  125. };
  126. // ## 'transition' CSS hook
  127. // Allows you to use the `transition` property in CSS.
  128. //
  129. // $("#hello").css({ transition: 'all 0 ease 0' });
  130. //
  131. $.cssHooks.transition = {
  132. get: function(elem) {
  133. return elem.style[support.transition];
  134. },
  135. set: function(elem, value) {
  136. elem.style[support.transition] = value;
  137. }
  138. };
  139. // ## Other CSS hooks
  140. // Allows you to rotate, scale and translate.
  141. registerCssHook('scale');
  142. registerCssHook('translate');
  143. registerCssHook('rotate');
  144. registerCssHook('rotateX');
  145. registerCssHook('rotateY');
  146. registerCssHook('rotate3d');
  147. registerCssHook('perspective');
  148. registerCssHook('skewX');
  149. registerCssHook('skewY');
  150. registerCssHook('x', true);
  151. registerCssHook('y', true);
  152. // ## Transform class
  153. // This is the main class of a transformation property that powers
  154. // `$.fn.css({ transform: '...' })`.
  155. //
  156. // This is, in essence, a dictionary object with key/values as `-transform`
  157. // properties.
  158. //
  159. // var t = new Transform("rotate(90) scale(4)");
  160. //
  161. // t.rotate //=> "90deg"
  162. // t.scale //=> "4,4"
  163. //
  164. // Setters are accounted for.
  165. //
  166. // t.set('rotate', 4)
  167. // t.rotate //=> "4deg"
  168. //
  169. // Convert it to a CSS string using the `toString()` and `toString(true)` (for WebKit)
  170. // functions.
  171. //
  172. // t.toString() //=> "rotate(90deg) scale(4,4)"
  173. // t.toString(true) //=> "rotate(90deg) scale3d(4,4,0)" (WebKit version)
  174. //
  175. function Transform(str) {
  176. if (typeof str === 'string') { this.parse(str); }
  177. return this;
  178. }
  179. Transform.prototype = {
  180. // ### setFromString()
  181. // Sets a property from a string.
  182. //
  183. // t.setFromString('scale', '2,4');
  184. // // Same as set('scale', '2', '4');
  185. //
  186. setFromString: function(prop, val) {
  187. var args =
  188. (typeof val === 'string') ? val.split(',') :
  189. (val.constructor === Array) ? val :
  190. [ val ];
  191. args.unshift(prop);
  192. Transform.prototype.set.apply(this, args);
  193. },
  194. // ### set()
  195. // Sets a property.
  196. //
  197. // t.set('scale', 2, 4);
  198. //
  199. set: function(prop) {
  200. var args = Array.prototype.slice.apply(arguments, [1]);
  201. if (this.setter[prop]) {
  202. this.setter[prop].apply(this, args);
  203. } else {
  204. this[prop] = args.join(',');
  205. }
  206. },
  207. get: function(prop) {
  208. if (this.getter[prop]) {
  209. return this.getter[prop].apply(this);
  210. } else {
  211. return this[prop] || 0;
  212. }
  213. },
  214. setter: {
  215. // ### rotate
  216. //
  217. // .css({ rotate: 30 })
  218. // .css({ rotate: "30" })
  219. // .css({ rotate: "30deg" })
  220. // .css({ rotate: "30deg" })
  221. //
  222. rotate: function(theta) {
  223. this.rotate = unit(theta, 'deg');
  224. },
  225. rotateX: function(theta) {
  226. this.rotateX = unit(theta, 'deg');
  227. },
  228. rotateY: function(theta) {
  229. this.rotateY = unit(theta, 'deg');
  230. },
  231. // ### scale
  232. //
  233. // .css({ scale: 9 }) //=> "scale(9,9)"
  234. // .css({ scale: '3,2' }) //=> "scale(3,2)"
  235. //
  236. scale: function(x, y) {
  237. if (y === undefined) { y = x; }
  238. this.scale = x + "," + y;
  239. },
  240. // ### skewX + skewY
  241. skewX: function(x) {
  242. this.skewX = unit(x, 'deg');
  243. },
  244. skewY: function(y) {
  245. this.skewY = unit(y, 'deg');
  246. },
  247. // ### perspectvie
  248. perspective: function(dist) {
  249. this.perspective = unit(dist, 'px');
  250. },
  251. // ### x / y
  252. // Translations. Notice how this keeps the other value.
  253. //
  254. // .css({ x: 4 }) //=> "translate(4px, 0)"
  255. // .css({ y: 10 }) //=> "translate(4px, 10px)"
  256. //
  257. x: function(x) {
  258. this.set('translate', x, null);
  259. },
  260. y: function(y) {
  261. this.set('translate', null, y);
  262. },
  263. // ### translate
  264. // Notice how this keeps the other value.
  265. //
  266. // .css({ translate: '2, 5' }) //=> "translate(2px, 5px)"
  267. //
  268. translate: function(x, y) {
  269. if (this._translateX === undefined) { this._translateX = 0; }
  270. if (this._translateY === undefined) { this._translateY = 0; }
  271. if (x !== null) { this._translateX = unit(x, 'px'); }
  272. if (y !== null) { this._translateY = unit(y, 'px'); }
  273. this.translate = this._translateX + "," + this._translateY;
  274. }
  275. },
  276. getter: {
  277. x: function() {
  278. return this._translateX || 0;
  279. },
  280. y: function() {
  281. return this._translateY || 0;
  282. },
  283. scale: function() {
  284. var s = (this.scale || "1,1").split(',');
  285. if (s[0]) { s[0] = parseFloat(s[0]); }
  286. if (s[1]) { s[1] = parseFloat(s[1]); }
  287. // "2.5,2.5" => 2.5
  288. // "2.5,1" => [2.5,1]
  289. return (s[0] === s[1]) ? s[0] : s;
  290. },
  291. rotate3d: function() {
  292. var s = (this.rotate3d || "0,0,0,0deg").split(',');
  293. for (var i=0; i<=3; ++i) {
  294. if (s[i]) { s[i] = parseFloat(s[i]); }
  295. }
  296. if (s[3]) { s[3] = unit(s[3], 'deg'); }
  297. return s;
  298. }
  299. },
  300. // ### parse()
  301. // Parses from a string. Called on constructor.
  302. parse: function(str) {
  303. var self = this;
  304. str.replace(/([a-zA-Z0-9]+)\((.*?)\)/g, function(x, prop, val) {
  305. self.setFromString(prop, val);
  306. });
  307. },
  308. // ### toString()
  309. // Converts to a `transition` CSS property string. If `use3d` is given,
  310. // it converts to a `-webkit-transition` CSS property string instead.
  311. toString: function(use3d) {
  312. var re = [];
  313. for (var i in this) {
  314. if (this.hasOwnProperty(i)) {
  315. // Don't use 3D transformations if the browser can't support it.
  316. if ((!support.transform3d) && (
  317. (i === 'rotateX') ||
  318. (i === 'rotateY') ||
  319. (i === 'perspective') ||
  320. (i === 'transformOrigin'))) { continue; }
  321. if (i[0] !== '_') {
  322. if (use3d && (i === 'scale')) {
  323. re.push(i + "3d(" + this[i] + ",1)");
  324. } else if (use3d && (i === 'translate')) {
  325. re.push(i + "3d(" + this[i] + ",0)");
  326. } else {
  327. re.push(i + "(" + this[i] + ")");
  328. }
  329. }
  330. }
  331. }
  332. return re.join(" ");
  333. }
  334. };
  335. function callOrQueue(self, queue, fn) {
  336. if (queue === true) {
  337. self.queue(fn);
  338. } else if (queue) {
  339. self.queue(queue, fn);
  340. } else {
  341. fn();
  342. }
  343. }
  344. // ### getProperties(dict)
  345. // Returns properties (for `transition-property`) for dictionary `props`. The
  346. // value of `props` is what you would expect in `$.css(...)`.
  347. function getProperties(props) {
  348. var re = [];
  349. $.each(props, function(key) {
  350. key = $.camelCase(key); // Convert "text-align" => "textAlign"
  351. key = $.transit.propertyMap[key] || key;
  352. key = uncamel(key); // Convert back to dasherized
  353. if ($.inArray(key, re) === -1) { re.push(key); }
  354. });
  355. return re;
  356. }
  357. // ### getTransition()
  358. // Returns the transition string to be used for the `transition` CSS property.
  359. //
  360. // Example:
  361. //
  362. // getTransition({ opacity: 1, rotate: 30 }, 500, 'ease');
  363. // //=> 'opacity 500ms ease, -webkit-transform 500ms ease'
  364. //
  365. function getTransition(properties, duration, easing, delay) {
  366. // Get the CSS properties needed.
  367. var props = getProperties(properties);
  368. // Account for aliases (`in` => `ease-in`).
  369. if ($.cssEase[easing]) { easing = $.cssEase[easing]; }
  370. // Build the duration/easing/delay attributes for it.
  371. var attribs = '' + toMS(duration) + ' ' + easing;
  372. if (parseInt(delay, 10) > 0) { attribs += ' ' + toMS(delay); }
  373. // For more properties, add them this way:
  374. // "margin 200ms ease, padding 200ms ease, ..."
  375. var transitions = [];
  376. $.each(props, function(i, name) {
  377. transitions.push(name + ' ' + attribs);
  378. });
  379. return transitions.join(', ');
  380. }
  381. // ## $.fn.transition
  382. // Works like $.fn.animate(), but uses CSS transitions.
  383. //
  384. // $("...").transition({ opacity: 0.1, scale: 0.3 });
  385. //
  386. // // Specific duration
  387. // $("...").transition({ opacity: 0.1, scale: 0.3 }, 500);
  388. //
  389. // // With duration and easing
  390. // $("...").transition({ opacity: 0.1, scale: 0.3 }, 500, 'in');
  391. //
  392. // // With callback
  393. // $("...").transition({ opacity: 0.1, scale: 0.3 }, function() { ... });
  394. //
  395. // // With everything
  396. // $("...").transition({ opacity: 0.1, scale: 0.3 }, 500, 'in', function() { ... });
  397. //
  398. // // Alternate syntax
  399. // $("...").transition({
  400. // opacity: 0.1,
  401. // duration: 200,
  402. // delay: 40,
  403. // easing: 'in',
  404. // complete: function() { /* ... */ }
  405. // });
  406. //
  407. $.fn.transition = $.fn.transit = function(properties, duration, easing, callback) {
  408. var self = this;
  409. var delay = 0;
  410. var queue = true;
  411. // Account for `.transition(properties, callback)`.
  412. if (typeof duration === 'function') {
  413. callback = duration;
  414. duration = undefined;
  415. }
  416. // Account for `.transition(properties, duration, callback)`.
  417. if (typeof easing === 'function') {
  418. callback = easing;
  419. easing = undefined;
  420. }
  421. // Alternate syntax.
  422. if (typeof properties.easing !== 'undefined') {
  423. easing = properties.easing;
  424. delete properties.easing;
  425. }
  426. if (typeof properties.duration !== 'undefined') {
  427. duration = properties.duration;
  428. delete properties.duration;
  429. }
  430. if (typeof properties.complete !== 'undefined') {
  431. callback = properties.complete;
  432. delete properties.complete;
  433. }
  434. if (typeof properties.queue !== 'undefined') {
  435. queue = properties.queue;
  436. delete properties.queue;
  437. }
  438. if (typeof properties.delay !== 'undefined') {
  439. delay = properties.delay;
  440. delete properties.delay;
  441. }
  442. // Set defaults. (`400` duration, `ease` easing)
  443. if (typeof duration === 'undefined') { duration = $.fx.speeds._default; }
  444. if (typeof easing === 'undefined') { easing = $.cssEase._default; }
  445. duration = toMS(duration);
  446. // Build the `transition` property.
  447. var transitionValue = getTransition(properties, duration, easing, delay);
  448. // Compute delay until callback.
  449. // If this becomes 0, don't bother setting the transition property.
  450. var work = $.transit.enabled && support.transition;
  451. var i = work ? (parseInt(duration, 10) + parseInt(delay, 10)) : 0;
  452. // If there's nothing to do...
  453. if (i === 0) {
  454. var fn = function(next) {
  455. self.css(properties);
  456. if (callback) { callback.apply(self); }
  457. if (next) { next(); }
  458. };
  459. callOrQueue(self, queue, fn);
  460. return self;
  461. }
  462. // Save the old transitions of each element so we can restore it later.
  463. var oldTransitions = {};
  464. var run = function(nextCall) {
  465. var bound = false;
  466. // Prepare the callback.
  467. var cb = function() {
  468. if (bound) { self.unbind(transitionEnd, cb); }
  469. if (i > 0) {
  470. self.each(function() {
  471. this.style[support.transition] = (oldTransitions[this] || null);
  472. });
  473. }
  474. if (typeof callback === 'function') { callback.apply(self); }
  475. if (typeof nextCall === 'function') { nextCall(); }
  476. };
  477. if ((i > 0) && (transitionEnd) && ($.transit.useTransitionEnd)) {
  478. // Use the 'transitionend' event if it's available.
  479. bound = true;
  480. self.bind(transitionEnd, cb);
  481. } else {
  482. // Fallback to timers if the 'transitionend' event isn't supported.
  483. window.setTimeout(cb, i);
  484. }
  485. // Apply transitions.
  486. self.each(function() {
  487. if (i > 0) {
  488. this.style[support.transition] = transitionValue;
  489. }
  490. $(this).css(properties);
  491. });
  492. };
  493. // Defer running. This allows the browser to paint any pending CSS it hasn't
  494. // painted yet before doing the transitions.
  495. var deferredRun = function(next) {
  496. var i = 0;
  497. // Durations that are too slow will get transitions mixed up.
  498. // (Tested on Mac/FF 7.0.1)
  499. if ((support.transition === 'MozTransition') && (i < 25)) { i = 25; }
  500. window.setTimeout(function() { run(next); }, i);
  501. };
  502. // Use jQuery's fx queue.
  503. callOrQueue(self, queue, deferredRun);
  504. // Chainability.
  505. return this;
  506. };
  507. function registerCssHook(prop, isPixels) {
  508. // For certain properties, the 'px' should not be implied.
  509. if (!isPixels) { $.cssNumber[prop] = true; }
  510. $.transit.propertyMap[prop] = support.transform;
  511. $.cssHooks[prop] = {
  512. get: function(elem) {
  513. var t = $(elem).css('transform') || new Transform();
  514. return t.get(prop);
  515. },
  516. set: function(elem, value) {
  517. var t = $(elem).css('transform') || new Transform();
  518. t.setFromString(prop, value);
  519. $(elem).css({ transform: t });
  520. }
  521. };
  522. }
  523. // ### uncamel(str)
  524. // Converts a camelcase string to a dasherized string.
  525. // (`marginLeft` => `margin-left`)
  526. function uncamel(str) {
  527. return str.replace(/([A-Z])/g, function(letter) { return '-' + letter.toLowerCase(); });
  528. }
  529. // ### unit(number, unit)
  530. // Ensures that number `number` has a unit. If no unit is found, assume the
  531. // default is `unit`.
  532. //
  533. // unit(2, 'px') //=> "2px"
  534. // unit("30deg", 'rad') //=> "30deg"
  535. //
  536. function unit(i, units) {
  537. if ((typeof i === "string") && (!i.match(/^[\-0-9\.]+$/))) {
  538. return i;
  539. } else {
  540. return "" + i + units;
  541. }
  542. }
  543. // ### toMS(duration)
  544. // Converts given `duration` to a millisecond string.
  545. //
  546. // toMS('fast') //=> '400ms'
  547. // toMS(10) //=> '10ms'
  548. //
  549. function toMS(duration) {
  550. var i = duration;
  551. // Allow for string durations like 'fast'.
  552. if ($.fx.speeds[i]) { i = $.fx.speeds[i]; }
  553. return unit(i, 'ms');
  554. }
  555. // Export some functions for testable-ness.
  556. $.transit.getTransitionValue = getTransition;
  557. })(jQuery);