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.

642 lines
19 KiB

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