You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

650 lines
20 KiB

  1. /* ******************************
  2. Semantic Module: Transition
  3. Author: Jack Lukic
  4. Notes: First Commit March 25, 2013
  5. A module for controlling css animations
  6. ****************************** */
  7. ;(function ( $, window, document, undefined ) {
  8. $.fn.transition = function() {
  9. var
  10. $allModules = $(this),
  11. moduleSelector = $allModules.selector || '',
  12. time = new Date().getTime(),
  13. performance = [],
  14. moduleArguments = arguments,
  15. query = moduleArguments[0],
  16. queryArguments = [].slice.call(arguments, 1),
  17. methodInvoked = (typeof query === 'string'),
  18. requestAnimationFrame = window.requestAnimationFrame
  19. || window.mozRequestAnimationFrame
  20. || window.webkitRequestAnimationFrame
  21. || window.msRequestAnimationFrame
  22. || function(callback) { setTimeout(callback, 0); },
  23. invokedResponse
  24. ;
  25. $allModules
  26. .each(function() {
  27. var
  28. $module = $(this),
  29. element = this,
  30. // set at run time
  31. settings,
  32. instance,
  33. error,
  34. className,
  35. metadata,
  36. animationEnd,
  37. animationName,
  38. namespace,
  39. moduleNamespace,
  40. module
  41. ;
  42. module = {
  43. initialize: function() {
  44. // get settings
  45. settings = module.get.settings.apply(element, moduleArguments);
  46. module.verbose('Converted arguments into settings object', settings);
  47. // set shortcuts
  48. error = settings.error;
  49. className = settings.className;
  50. namespace = settings.namespace;
  51. metadata = settings.metadata;
  52. moduleNamespace = 'module-' + namespace;
  53. animationEnd = module.get.animationEvent();
  54. animationName = module.get.animationName();
  55. instance = $module.data(moduleNamespace);
  56. if(instance === undefined) {
  57. module.instantiate();
  58. }
  59. if(methodInvoked) {
  60. methodInvoked = module.invoke(query);
  61. }
  62. // no internal method was found matching query or query not made
  63. if(methodInvoked === false) {
  64. module.animate();
  65. }
  66. },
  67. instantiate: function() {
  68. module.verbose('Storing instance of module', module);
  69. instance = module;
  70. $module
  71. .data(moduleNamespace, instance)
  72. ;
  73. },
  74. destroy: function() {
  75. module.verbose('Destroying previous module for', element);
  76. $module
  77. .removeData(moduleNamespace)
  78. ;
  79. },
  80. animate: function(overrideSettings) {
  81. settings = overrideSettings || settings;
  82. module.debug('Preparing animation', settings.animation);
  83. if(module.is.animating()) {
  84. if(settings.queue) {
  85. module.queue(settings.animation);
  86. }
  87. return false;
  88. }
  89. module.save.conditions();
  90. module.set.duration(settings.duration);
  91. module.set.animating();
  92. module.repaint();
  93. $module
  94. .addClass(className.transition)
  95. .addClass(settings.animation)
  96. .one(animationEnd, module.complete)
  97. ;
  98. if(!module.has.direction() && module.can.transition()) {
  99. module.set.direction();
  100. }
  101. if(!module.can.animate()) {
  102. module.restore.conditions();
  103. module.error(error.noAnimation);
  104. return false;
  105. }
  106. module.show();
  107. module.debug('Starting tween', settings.animation, $module.attr('class'));
  108. },
  109. queue: function(animation) {
  110. module.debug('Queueing animation of', animation);
  111. instance.queuing = true;
  112. $module
  113. .one(animationEnd, function() {
  114. instance.queuing = false;
  115. module.animate.apply(this, settings);
  116. })
  117. ;
  118. },
  119. complete: function () {
  120. module.verbose('CSS animation complete', settings.animation);
  121. if(!module.is.looping()) {
  122. if($module.hasClass(className.outward)) {
  123. module.restore.conditions();
  124. module.hide();
  125. }
  126. else if($module.hasClass(className.inward)) {
  127. module.restore.conditions();
  128. module.show();
  129. }
  130. else {
  131. module.restore.conditions();
  132. }
  133. module.remove.animating();
  134. }
  135. $.proxy(settings.complete, this)();
  136. },
  137. repaint: function(fakeAssignment) {
  138. module.verbose('Forcing repaint event');
  139. fakeAssignment = element.offsetWidth;
  140. },
  141. has: {
  142. direction: function(animation) {
  143. animation = animation || settings.animation;
  144. if( $module.hasClass(className.inward) || $module.hasClass(className.outward) ) {
  145. return true;
  146. }
  147. }
  148. },
  149. set: {
  150. animating: function() {
  151. $module.addClass(className.animating);
  152. },
  153. direction: function() {
  154. if($module.is(':visible')) {
  155. module.debug('Automatically determining the direction of animation', 'Outward');
  156. $module
  157. .addClass(className.outward)
  158. .removeClass(className.inward)
  159. ;
  160. }
  161. else {
  162. module.debug('Automatically determining the direction of animation', 'Inward');
  163. $module
  164. .addClass(className.inward)
  165. .removeClass(className.outward)
  166. ;
  167. }
  168. },
  169. looping: function() {
  170. module.debug('Transition set to loop');
  171. $module
  172. .addClass(className.looping)
  173. ;
  174. },
  175. duration: function(duration) {
  176. duration = duration || settings.duration;
  177. duration = (typeof duration == 'number')
  178. ? duration + 'ms'
  179. : duration
  180. ;
  181. module.verbose('Setting animation duration', duration);
  182. $module
  183. .css({
  184. '-webkit-animation-duration': duration,
  185. '-moz-animation-duration': duration,
  186. '-ms-animation-duration': duration,
  187. '-o-animation-duration': duration,
  188. 'animation-duration': duration
  189. })
  190. ;
  191. }
  192. },
  193. save: {
  194. conditions: function() {
  195. module.cache = {
  196. className : $module.attr('class'),
  197. style : $module.attr('style')
  198. };
  199. module.verbose('Saving original attributes', module.cache);
  200. }
  201. },
  202. restore: {
  203. conditions: function() {
  204. if(typeof module.cache === undefined) {
  205. module.error(error.cache);
  206. return false;
  207. }
  208. if(module.cache.className) {
  209. $module.attr('class', module.cache.className);
  210. }
  211. else {
  212. $module.removeAttr('class');
  213. }
  214. if(module.cache.style) {
  215. $module.attr('style', module.cache.style);
  216. }
  217. else {
  218. $module.removeAttr('style');
  219. }
  220. if(module.is.looping()) {
  221. module.remove.looping();
  222. }
  223. module.verbose('Restoring original attributes', module.cache);
  224. }
  225. },
  226. remove: {
  227. animating: function() {
  228. $module.removeClass(className.animating);
  229. },
  230. looping: function() {
  231. module.debug('Transitions are no longer looping');
  232. $module
  233. .removeClass(className.looping)
  234. ;
  235. module.repaint();
  236. }
  237. },
  238. get: {
  239. settings: function(animation, duration, complete) {
  240. // single settings object
  241. if($.isPlainObject(animation)) {
  242. return $.extend(true, {}, $.fn.transition.settings, animation);
  243. }
  244. // all arguments provided
  245. else if(typeof complete == 'function') {
  246. return $.extend(true, {}, $.fn.transition.settings, {
  247. animation : animation,
  248. complete : complete,
  249. duration : duration
  250. });
  251. }
  252. // only duration provided
  253. else if(typeof duration == 'string' || typeof duration == 'number') {
  254. return $.extend(true, {}, $.fn.transition.settings, {
  255. animation : animation,
  256. duration : duration
  257. });
  258. }
  259. // duration is actually settings object
  260. else if(typeof duration == 'object') {
  261. return $.extend(true, {}, $.fn.transition.settings, duration, {
  262. animation : animation
  263. });
  264. }
  265. // duration is actually callback
  266. else if(typeof duration == 'function') {
  267. return $.extend(true, {}, $.fn.transition.settings, {
  268. animation : animation,
  269. complete : duration
  270. });
  271. }
  272. // only animation provided
  273. else {
  274. return $.extend(true, {}, $.fn.transition.settings, {
  275. animation : animation
  276. });
  277. }
  278. return $.fn.transition.settings;
  279. },
  280. animationName: function() {
  281. var
  282. element = document.createElement('div'),
  283. animations = {
  284. 'animation' :'animationName',
  285. 'OAnimation' :'oAnimationName',
  286. 'MozAnimation' :'mozAnimationName',
  287. 'WebkitAnimation' :'webkitAnimationName'
  288. },
  289. animation
  290. ;
  291. for(animation in animations){
  292. if( element.style[animation] !== undefined ){
  293. module.verbose('Determining animation vendor name property', animations[animation]);
  294. return animations[animation];
  295. }
  296. }
  297. return false;
  298. },
  299. animationEvent: function() {
  300. var
  301. element = document.createElement('div'),
  302. animations = {
  303. 'animation' :'animationend',
  304. 'OAnimation' :'oAnimationEnd',
  305. 'MozAnimation' :'mozAnimationEnd',
  306. 'WebkitAnimation' :'webkitAnimationEnd'
  307. },
  308. animation
  309. ;
  310. for(animation in animations){
  311. if( element.style[animation] !== undefined ){
  312. module.verbose('Determining animation vendor end event', animations[animation]);
  313. return animations[animation];
  314. }
  315. }
  316. return false;
  317. }
  318. },
  319. can: {
  320. animate: function() {
  321. if($module.css(animationName) !== 'none') {
  322. module.debug('CSS definition found');
  323. return true;
  324. }
  325. else {
  326. module.debug('Unable to find css definition');
  327. return false;
  328. }
  329. },
  330. transition: function() {
  331. var
  332. $clone = $('<div>').addClass( $module.attr('class') ).appendTo($('body')),
  333. currentAnimation = $clone.css(animationName),
  334. inAnimation = $clone.addClass(className.inward).css(animationName)
  335. ;
  336. if(currentAnimation != inAnimation) {
  337. module.debug('In/out transitions exist');
  338. $clone.remove();
  339. return true;
  340. }
  341. else {
  342. module.debug('Static animation found');
  343. $clone.remove();
  344. return false;
  345. }
  346. }
  347. },
  348. is: {
  349. animating: function() {
  350. return $module.hasClass(className.animating);
  351. },
  352. looping: function() {
  353. return $module.hasClass(className.looping);
  354. },
  355. visible: function() {
  356. return $module.is(':visible');
  357. }
  358. },
  359. hide: function() {
  360. module.verbose('Hiding element');
  361. $module
  362. .removeClass(className.visible)
  363. .addClass(className.transition)
  364. .addClass(className.hidden)
  365. ;
  366. },
  367. show: function() {
  368. module.verbose('Showing element');
  369. $module
  370. .removeClass(className.hidden)
  371. .addClass(className.transition)
  372. .addClass(className.visible)
  373. ;
  374. },
  375. start: function() {
  376. module.verbose('Starting animation');
  377. $module.removeClass(className.disabled);
  378. },
  379. stop: function() {
  380. module.debug('Stopping animation');
  381. $module.addClass(className.disabled);
  382. },
  383. toggle: function() {
  384. module.debug('Toggling play status');
  385. $module.toggleClass(className.disabled);
  386. },
  387. setting: function(name, value) {
  388. if(value !== undefined) {
  389. if( $.isPlainObject(name) ) {
  390. $.extend(true, settings, name);
  391. }
  392. else {
  393. settings[name] = value;
  394. }
  395. }
  396. else {
  397. return settings[name];
  398. }
  399. },
  400. internal: function(name, value) {
  401. if(value !== undefined) {
  402. if( $.isPlainObject(name) ) {
  403. $.extend(true, module, name);
  404. }
  405. else {
  406. module[name] = value;
  407. }
  408. }
  409. else {
  410. return module[name];
  411. }
  412. },
  413. debug: function() {
  414. if(settings.debug) {
  415. if(settings.performance) {
  416. module.performance.log(arguments);
  417. }
  418. else {
  419. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  420. module.debug.apply(console, arguments);
  421. }
  422. }
  423. },
  424. verbose: function() {
  425. if(settings.verbose && settings.debug) {
  426. if(settings.performance) {
  427. module.performance.log(arguments);
  428. }
  429. else {
  430. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  431. module.verbose.apply(console, arguments);
  432. }
  433. }
  434. },
  435. error: function() {
  436. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  437. module.error.apply(console, arguments);
  438. },
  439. performance: {
  440. log: function(message) {
  441. var
  442. currentTime,
  443. executionTime,
  444. previousTime
  445. ;
  446. if(settings.performance) {
  447. currentTime = new Date().getTime();
  448. previousTime = time || currentTime;
  449. executionTime = currentTime - previousTime;
  450. time = currentTime;
  451. performance.push({
  452. 'Element' : element,
  453. 'Name' : message[0],
  454. 'Arguments' : [].slice.call(message, 1) || '',
  455. 'Execution Time' : executionTime
  456. });
  457. }
  458. clearTimeout(module.performance.timer);
  459. module.performance.timer = setTimeout(module.performance.display, 100);
  460. },
  461. display: function() {
  462. var
  463. title = settings.name + ':',
  464. totalTime = 0
  465. ;
  466. time = false;
  467. clearTimeout(module.performance.timer);
  468. $.each(performance, function(index, data) {
  469. totalTime += data['Execution Time'];
  470. });
  471. title += ' ' + totalTime + 'ms';
  472. if(moduleSelector) {
  473. title += ' \'' + moduleSelector + '\'';
  474. }
  475. if($allModules.size() > 1) {
  476. title += ' ' + '(' + $allModules.size() + ')';
  477. }
  478. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  479. console.groupCollapsed(title);
  480. if(console.table) {
  481. console.table(performance);
  482. }
  483. else {
  484. $.each(performance, function(index, data) {
  485. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  486. });
  487. }
  488. console.groupEnd();
  489. }
  490. performance = [];
  491. }
  492. },
  493. invoke: function(query, passedArguments, context) {
  494. var
  495. maxDepth,
  496. found,
  497. response
  498. ;
  499. passedArguments = passedArguments || queryArguments;
  500. context = element || context;
  501. if(typeof query == 'string' && instance !== undefined) {
  502. query = query.split(/[\. ]/);
  503. maxDepth = query.length - 1;
  504. $.each(query, function(depth, value) {
  505. var camelCaseValue = (depth != maxDepth)
  506. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  507. : query
  508. ;
  509. if( $.isPlainObject( instance[value] ) && (depth != maxDepth) ) {
  510. instance = instance[value];
  511. }
  512. else if( $.isPlainObject( instance[camelCaseValue] ) && (depth != maxDepth) ) {
  513. instance = instance[camelCaseValue];
  514. }
  515. else if( instance[value] !== undefined ) {
  516. found = instance[value];
  517. return false;
  518. }
  519. else if( instance[camelCaseValue] !== undefined ) {
  520. found = instance[camelCaseValue];
  521. return false;
  522. }
  523. else {
  524. return false;
  525. }
  526. });
  527. }
  528. if ( $.isFunction( found ) ) {
  529. response = found.apply(context, passedArguments);
  530. }
  531. else if(found !== undefined) {
  532. response = found;
  533. }
  534. if($.isArray(invokedResponse)) {
  535. invokedResponse.push(response);
  536. }
  537. else if(typeof invokedResponse == 'string') {
  538. invokedResponse = [invokedResponse, response];
  539. }
  540. else if(response !== undefined) {
  541. invokedResponse = response;
  542. }
  543. return found || false;
  544. }
  545. };
  546. module.initialize();
  547. })
  548. ;
  549. return (invokedResponse !== undefined)
  550. ? invokedResponse
  551. : this
  552. ;
  553. };
  554. $.fn.transition.settings = {
  555. // module info
  556. name : 'Transition',
  557. // debug content outputted to console
  558. debug : true,
  559. // verbose debug output
  560. verbose : true,
  561. // performance data output
  562. performance : true,
  563. // event namespace
  564. namespace : 'transition',
  565. // animation complete event
  566. complete : function() {},
  567. // animation duration (useful only with future js animations)
  568. animation : 'fade',
  569. duration : '700ms',
  570. // queue up animations
  571. queue : true,
  572. className : {
  573. transition : 'ui transition',
  574. animating : 'animating',
  575. looping : 'looping',
  576. loading : 'loading',
  577. disabled : 'disabled',
  578. hidden : 'hidden',
  579. visible : 'visible',
  580. inward : 'in',
  581. outward : 'out'
  582. },
  583. // possible errors
  584. error: {
  585. noAnimation : 'There is no css animation matching the one you specified.',
  586. method : 'The method you called is not defined'
  587. }
  588. };
  589. })( jQuery, window , document );