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.

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