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.

503 lines
14 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. transitionEnd,
  37. namespace,
  38. moduleNamespace,
  39. module
  40. ;
  41. module = {
  42. initialize: function() {
  43. // get settings
  44. settings = module.get.settings.apply(element, moduleArguments);
  45. module.verbose('Converted arguments into settings object', settings);
  46. // set shortcuts
  47. error = settings.error;
  48. className = settings.className;
  49. namespace = settings.namespace;
  50. metadata = settings.metadata;
  51. moduleNamespace = 'module-' + namespace;
  52. transitionEnd = module.get.transitionEvent();
  53. instance = $module.data(moduleNamespace);
  54. if(instance === undefined) {
  55. module.instantiate();
  56. }
  57. if(methodInvoked) {
  58. invokedResponse = module.invoke(query);
  59. }
  60. // no internal method was found matching query or query not made
  61. if(!methodInvoked || invokedResponse === false) {
  62. module.animate();
  63. }
  64. },
  65. instantiate: function() {
  66. module.verbose('Storing instance of module', module);
  67. instance = module;
  68. $module
  69. .data(moduleNamespace, instance)
  70. ;
  71. },
  72. destroy: function() {
  73. module.verbose('Destroying previous module for', element);
  74. $module
  75. .removeData(moduleNamespace)
  76. ;
  77. },
  78. repaint: function(fakeAssignment) {
  79. module.verbose('Forcing repaint event');
  80. fakeAssignment = element.offsetWidth;
  81. },
  82. set: {
  83. animating: function() {
  84. $module.addClass(className.animating);
  85. },
  86. direction: function() {
  87. return $module.is(':visible')
  88. ? 'out'
  89. : 'in'
  90. ;
  91. }
  92. loop: function() {
  93. $module
  94. .addClass(className.loop)
  95. ;
  96. },
  97. duration: function(duration) {
  98. duration = duration || settings.duration;
  99. module.verbose('Setting animation duration', duration);
  100. $module
  101. .css({
  102. '-webkit-animation-duration': duration,
  103. '-moz-animation-duration': duration,
  104. '-ms-animation-duration': duration,
  105. '-o-animation-duration': duration,
  106. 'animation-duration': duration
  107. })
  108. ;
  109. }
  110. },
  111. remove: {
  112. animating: function() {
  113. $module.removeClass(className.animating);
  114. }
  115. },
  116. get: {
  117. settings: function(animation, duration, complete) {
  118. // single settings object
  119. if($.isPlainObject(animation) === undefined) {
  120. return $.extend(true, {}, $.fn.transition.settings, animation);
  121. }
  122. // all arguments provided
  123. else if(typeof complete == 'function') {
  124. return $.extend(true, {}, $.fn.transition.settings, {
  125. animation : animation,
  126. complete : complete,
  127. duration : duration
  128. });
  129. }
  130. // only duration provided
  131. else if(typeof duration == 'string') {
  132. return $.extend(true, {}, $.fn.transition.settings, {
  133. animation : animation,
  134. duration : duration
  135. });
  136. }
  137. // duration is actually settings object
  138. else if(typeof duration == 'object') {
  139. return $.extend(true, {}, $.fn.transition.settings, duration, {
  140. animation : animation
  141. });
  142. }
  143. // duration is actually callback
  144. else if(typeof duration == 'function') {
  145. return $.extend(true, {}, $.fn.transition.settings, {
  146. animation : animation,
  147. complete : duration
  148. });
  149. }
  150. // only animation provided
  151. else {
  152. return $.extend(true, {}, $.fn.transition.settings, {
  153. animation : animation
  154. });
  155. }
  156. return $.fn.transition.settings;
  157. },
  158. transitionEvent: function() {
  159. var
  160. element = document.createElement('element'),
  161. animations = {
  162. 'animation' :'animationend',
  163. 'OAnimation' :'oAnimationEnd',
  164. 'MozAnimation' :'mozAnimationEnd',
  165. 'WebkitAnimation' :'webkitAnimationEnd'
  166. },
  167. animation
  168. ;
  169. for(animation in animations){
  170. if( element.style[animation] !== undefined ){
  171. module.verbose('Determining animation end event', animations[animation]);
  172. return animations[animation];
  173. }
  174. }
  175. return false;
  176. }
  177. },
  178. can: {
  179. animate: function(animation) {
  180. var
  181. $fake = $('<div />')
  182. ;
  183. animation = animation || settings.animation;
  184. $fake
  185. .addClass(className.transition)
  186. .addClass(animation)
  187. ;
  188. return $fake.css('animationName') !== 'none';
  189. }
  190. },
  191. is: {
  192. animating: function() {
  193. return $module.hasClass(className.animating);
  194. }
  195. },
  196. hide: function() {
  197. module.verbose('Hiding element');
  198. $module
  199. .addClass(className.transition)
  200. .addClass(className.hidden)
  201. ;
  202. },
  203. show: function() {
  204. module.verbose('Showing element');
  205. $module
  206. .removeClass(className.hidden)
  207. ;
  208. },
  209. start: function() {
  210. module.verbose('Starting animation');
  211. $module.removeClass(className.disabled);
  212. },
  213. stop: function() {
  214. module.debug('Stopping animation');
  215. $module.addClass(className.disabled);
  216. },
  217. toggle: function() {
  218. module.debug('Toggling play status');
  219. $module.toggleClass(className.disabled);
  220. },
  221. animate: function(overrideSettings) {
  222. settings = overrideSettings || settings;
  223. if(!module.can.animate()) {
  224. module.error(error.noAnimation, settings.animation);
  225. return false;
  226. }
  227. if(module.is.animating()) {
  228. module.queue(settings.animation);
  229. return false;
  230. }
  231. module.set.duration();
  232. module.show();
  233. module.originalClass = $module.attr('class');
  234. module.originalStyle = $module.attr('style');
  235. module.repaint();
  236. module.set.direction();
  237. module.set.animating();
  238. $module
  239. .addClass(className.transition)
  240. .addClass(settings.animation)
  241. .one(transitionEnd, module.complete)
  242. ;
  243. module.debug('Beginning animation', settings.animation, $module.attr('class'));
  244. },
  245. queue: function(animation) {
  246. module.debug('Queueing animation of', animation);
  247. $module
  248. .one(transitionEnd, function() {
  249. module.animate.apply(this, settings);
  250. })
  251. ;
  252. },
  253. reset: function() {
  254. module.verbose('Resetting original class', module.originalClass);
  255. $module
  256. .attr('style', module.originalStyle)
  257. .attr('class', module.originalClass)
  258. ;
  259. },
  260. complete: function () {
  261. module.verbose('CSS animation complete', settings.animation);
  262. if($module.hasClass(className.outward)) {
  263. module.reset();
  264. module.hide();
  265. }
  266. else if($module.hasClass(className.inward)) {
  267. module.reset();
  268. module.show();
  269. }
  270. else {
  271. module.reset();
  272. }
  273. module.remove.animating();
  274. module.repaint();
  275. settings.complete();
  276. },
  277. setting: function(name, value) {
  278. if(value !== undefined) {
  279. if( $.isPlainObject(name) ) {
  280. $.extend(true, settings, name);
  281. }
  282. else {
  283. settings[name] = value;
  284. }
  285. }
  286. else {
  287. return settings[name];
  288. }
  289. },
  290. internal: function(name, value) {
  291. if(value !== undefined) {
  292. if( $.isPlainObject(name) ) {
  293. $.extend(true, module, name);
  294. }
  295. else {
  296. module[name] = value;
  297. }
  298. }
  299. else {
  300. return module[name];
  301. }
  302. },
  303. debug: function() {
  304. if(settings.debug) {
  305. if(settings.performance) {
  306. module.performance.log(arguments);
  307. }
  308. else {
  309. module.debug = Function.prototype.bind.call(console.info, console, settings.moduleName + ':');
  310. }
  311. }
  312. },
  313. verbose: function() {
  314. if(settings.verbose && settings.debug) {
  315. if(settings.performance) {
  316. module.performance.log(arguments);
  317. }
  318. else {
  319. module.verbose = Function.prototype.bind.call(console.info, console, settings.moduleName + ':');
  320. }
  321. }
  322. },
  323. error: function() {
  324. module.error = Function.prototype.bind.call(console.error, console, settings.moduleName + ':');
  325. },
  326. performance: {
  327. log: function(message) {
  328. var
  329. currentTime,
  330. executionTime,
  331. previousTime
  332. ;
  333. if(settings.performance) {
  334. currentTime = new Date().getTime();
  335. previousTime = time || currentTime;
  336. executionTime = currentTime - previousTime;
  337. time = currentTime;
  338. performance.push({
  339. 'Element' : element,
  340. 'Name' : message[0],
  341. 'Arguments' : [].slice.call(message, 1) || '',
  342. 'Execution Time' : executionTime
  343. });
  344. }
  345. clearTimeout(module.performance.timer);
  346. module.performance.timer = setTimeout(module.performance.display, 100);
  347. },
  348. display: function() {
  349. var
  350. title = settings.moduleName + ':',
  351. totalTime = 0
  352. ;
  353. time = false;
  354. clearTimeout(module.performance.timer);
  355. $.each(performance, function(index, data) {
  356. totalTime += data['Execution Time'];
  357. });
  358. title += ' ' + totalTime + 'ms';
  359. if(moduleSelector) {
  360. title += ' \'' + moduleSelector + '\'';
  361. }
  362. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  363. console.groupCollapsed(title);
  364. if(console.table) {
  365. console.table(performance);
  366. }
  367. else {
  368. $.each(performance, function(index, data) {
  369. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  370. });
  371. }
  372. console.groupEnd();
  373. }
  374. performance = [];
  375. }
  376. },
  377. invoke: function(query, passedArguments, context) {
  378. var
  379. searchInstance = instance,
  380. maxDepth,
  381. found
  382. ;
  383. passedArguments = passedArguments || queryArguments;
  384. context = element || context;
  385. if(typeof query == 'string' && searchInstance !== undefined) {
  386. query = query.split(/[\. ]/);
  387. maxDepth = query.length - 1;
  388. $.each(query, function(depth, value) {
  389. if( $.isPlainObject( searchInstance[value] ) && (depth != maxDepth) ) {
  390. searchInstance = searchInstance[value];
  391. return true;
  392. }
  393. else if( searchInstance[value] !== undefined ) {
  394. found = searchInstance[value];
  395. return true;
  396. }
  397. module.error(error.method);
  398. return false;
  399. });
  400. }
  401. if ( $.isFunction( found ) ) {
  402. instance.verbose('Executing invoked function', found);
  403. return found.apply(context, passedArguments);
  404. }
  405. return found || false;
  406. }
  407. };
  408. module.initialize();
  409. })
  410. ;
  411. return (invokedResponse)
  412. ? invokedResponse
  413. : this
  414. ;
  415. };
  416. $.fn.transition.settings = {
  417. // module info
  418. moduleName : 'Transition',
  419. // debug content outputted to console
  420. debug : true,
  421. // verbose debug output
  422. verbose : true,
  423. // performance data output
  424. performance : true,
  425. // event namespace
  426. namespace : 'transition',
  427. // animation complete event
  428. complete : function() {},
  429. // animation duration (useful only with future js animations)
  430. animation : 'fade',
  431. duration : '1s',
  432. className : {
  433. animating : 'animating',
  434. disabled : 'disabled',
  435. hidden : 'hidden',
  436. inward : 'in',
  437. looping : 'looping',
  438. outward : 'out',
  439. transition : 'ui transition'
  440. },
  441. // possible errors
  442. error: {
  443. noAnimation : 'There is no css animation matching the one you specified.',
  444. method : 'The method you called is not defined'
  445. }
  446. };
  447. })( jQuery, window , document );