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.

654 lines
20 KiB

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