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.

849 lines
27 KiB

10 years ago
  1. /*
  2. * # Semantic - Transition
  3. * http://github.com/semantic-org/semantic-ui/
  4. *
  5. *
  6. * Copyright 2014 Contributor
  7. * Released under the MIT license
  8. * http://opensource.org/licenses/MIT
  9. *
  10. */
  11. ;(function ( $, window, document, undefined ) {
  12. "use strict";
  13. $.fn.transition = function() {
  14. var
  15. $allModules = $(this),
  16. moduleSelector = $allModules.selector || '',
  17. time = new Date().getTime(),
  18. performance = [],
  19. moduleArguments = arguments,
  20. query = moduleArguments[0],
  21. queryArguments = [].slice.call(arguments, 1),
  22. methodInvoked = (typeof query === 'string'),
  23. requestAnimationFrame = window.requestAnimationFrame
  24. || window.mozRequestAnimationFrame
  25. || window.webkitRequestAnimationFrame
  26. || window.msRequestAnimationFrame
  27. || function(callback) { setTimeout(callback, 0); },
  28. returnedValue
  29. ;
  30. $allModules
  31. .each(function() {
  32. var
  33. $module = $(this),
  34. element = this,
  35. // set at run time
  36. settings,
  37. instance,
  38. error,
  39. className,
  40. metadata,
  41. animationEnd,
  42. animationName,
  43. namespace,
  44. moduleNamespace,
  45. eventNamespace,
  46. module
  47. ;
  48. module = {
  49. initialize: function() {
  50. // get full settings
  51. moduleNamespace = 'module-' + namespace;
  52. settings = module.get.settings.apply(element, moduleArguments);
  53. className = settings.className;
  54. metadata = settings.metadata;
  55. animationEnd = module.get.animationEvent();
  56. animationName = module.get.animationName();
  57. error = settings.error;
  58. namespace = settings.namespace;
  59. eventNamespace = '.' + settings.namespace;
  60. instance = $module.data(moduleNamespace) || module;
  61. if(methodInvoked) {
  62. methodInvoked = module.invoke(query);
  63. }
  64. // no internal method was found matching query or query not made
  65. if(methodInvoked === false) {
  66. module.verbose('Converted arguments into settings object', settings);
  67. module.animate();
  68. module.instantiate();
  69. }
  70. },
  71. instantiate: function() {
  72. module.verbose('Storing instance of module', module);
  73. $module
  74. .data(moduleNamespace, instance)
  75. ;
  76. },
  77. destroy: function() {
  78. module.verbose('Destroying previous module for', element);
  79. $module
  80. .removeData(moduleNamespace)
  81. ;
  82. },
  83. refresh: function() {
  84. module.verbose('Refreshing display type on next animation');
  85. delete module.displayType;
  86. },
  87. forceRepaint: function() {
  88. module.verbose('Forcing element repaint');
  89. var
  90. $parentElement = $module.parent(),
  91. $nextElement = $module.next()
  92. ;
  93. if($nextElement.size() === 0) {
  94. $module.detach().appendTo($parentElement);
  95. }
  96. else {
  97. $module.detach().insertBefore($nextElement);
  98. }
  99. },
  100. repaint: function() {
  101. module.verbose('Repainting element');
  102. var
  103. fakeAssignment = element.offsetWidth
  104. ;
  105. },
  106. animate: function(overrideSettings) {
  107. settings = overrideSettings || settings;
  108. if(!module.is.supported()) {
  109. module.error(error.support);
  110. return false;
  111. }
  112. module.debug('Preparing animation', settings.animation);
  113. if(module.is.animating()) {
  114. if(settings.queue) {
  115. if(!settings.allowRepeats && module.has.direction() && module.is.occuring() && module.queuing !== true) {
  116. module.error(error.repeated, settings.animation, $module);
  117. }
  118. else {
  119. module.queue(settings.animation);
  120. }
  121. return false;
  122. }
  123. else {
  124. }
  125. }
  126. if(module.can.animate) {
  127. module.set.animating(settings.animation);
  128. }
  129. else {
  130. module.error(error.noAnimation, settings.animation);
  131. }
  132. },
  133. reset: function() {
  134. module.debug('Resetting animation to beginning conditions');
  135. $module.off(animationEnd + eventNamespace);
  136. module.restore.conditions();
  137. module.hide();
  138. module.remove.animating();
  139. },
  140. queue: function(animation) {
  141. module.debug('Queueing animation of', animation);
  142. module.queuing = true;
  143. $module
  144. .one(animationEnd + eventNamespace, function() {
  145. module.queuing = false;
  146. module.repaint();
  147. module.animate.apply(this, settings);
  148. })
  149. ;
  150. },
  151. complete: function () {
  152. module.verbose('CSS animation complete', settings.animation);
  153. if(!module.is.looping()) {
  154. if( module.is.outward() ) {
  155. module.verbose('Animation is outward, hiding element');
  156. module.restore.conditions();
  157. module.remove.display();
  158. module.hide();
  159. $.proxy(settings.onHide, this)();
  160. }
  161. else if( module.is.inward() ) {
  162. module.verbose('Animation is outward, showing element');
  163. module.restore.conditions();
  164. module.set.display();
  165. module.show();
  166. $.proxy(settings.onShow, this)();
  167. }
  168. else {
  169. module.restore.conditions();
  170. }
  171. module.remove.duration();
  172. module.remove.animating();
  173. }
  174. $.proxy(settings.complete, this)();
  175. },
  176. has: {
  177. direction: function(animation) {
  178. animation = animation || settings.animation;
  179. if( animation.search(className.inward) !== -1 || animation.search(className.outward) !== -1) {
  180. module.debug('Direction already set in animation');
  181. return true;
  182. }
  183. return false;
  184. }
  185. },
  186. set: {
  187. animating: function(animation) {
  188. animation = animation || settings.animation;
  189. if(!module.is.animating()) {
  190. module.save.conditions();
  191. }
  192. module.remove.direction();
  193. $module.off('.complete');
  194. if(module.can.transition() && !module.has.direction()) {
  195. module.set.direction();
  196. }
  197. module.remove.hidden();
  198. module.set.display();
  199. $module
  200. .addClass(className.animating)
  201. .addClass(className.transition)
  202. .addClass(animation)
  203. .one(animationEnd + '.complete' + eventNamespace, module.complete)
  204. ;
  205. module.set.duration(settings.duration);
  206. $.proxy(settings.start, this)();
  207. module.debug('Starting tween', animation, $module.attr('class'));
  208. },
  209. display: function() {
  210. var
  211. style = module.get.style(),
  212. displayType = module.get.displayType(),
  213. overrideStyle = style + 'display: ' + displayType + ' !important;'
  214. ;
  215. if( $module.css('display') !== displayType ) {
  216. module.verbose('Setting inline visibility to', displayType);
  217. $module
  218. .attr('style', overrideStyle)
  219. ;
  220. }
  221. },
  222. direction: function() {
  223. if($module.is(':visible') && !module.is.hidden()) {
  224. module.debug('Automatically determining the direction of animation', 'Outward');
  225. $module
  226. .removeClass(className.inward)
  227. .addClass(className.outward)
  228. ;
  229. }
  230. else {
  231. module.debug('Automatically determining the direction of animation', 'Inward');
  232. $module
  233. .removeClass(className.outward)
  234. .addClass(className.inward)
  235. ;
  236. }
  237. },
  238. looping: function() {
  239. module.debug('Transition set to loop');
  240. $module
  241. .addClass(className.looping)
  242. ;
  243. },
  244. duration: function(duration) {
  245. duration = duration || settings.duration;
  246. duration = (typeof duration == 'number')
  247. ? duration + 'ms'
  248. : duration
  249. ;
  250. module.verbose('Setting animation duration', duration);
  251. $module
  252. .css({
  253. '-webkit-animation-duration': duration,
  254. '-moz-animation-duration': duration,
  255. '-ms-animation-duration': duration,
  256. '-o-animation-duration': duration,
  257. 'animation-duration': duration
  258. })
  259. ;
  260. },
  261. hidden: function() {
  262. if(!module.is.hidden()) {
  263. $module
  264. .addClass(className.transition)
  265. .addClass(className.hidden)
  266. ;
  267. if($module.css('display') !== 'none') {
  268. module.verbose('Overriding default display to hide element');
  269. $module
  270. .css('display', 'none')
  271. ;
  272. }
  273. }
  274. },
  275. visible: function() {
  276. $module
  277. .addClass(className.transition)
  278. .addClass(className.visible)
  279. ;
  280. }
  281. },
  282. save: {
  283. displayType: function(displayType) {
  284. module.displayType = displayType;
  285. },
  286. transitionExists: function(animation, exists) {
  287. $.fn.transition.exists[animation] = exists;
  288. module.verbose('Saving existence of transition', animation, exists);
  289. },
  290. conditions: function() {
  291. var
  292. clasName = $module.attr('class') || false,
  293. style = $module.attr('style') || ''
  294. ;
  295. $module.removeClass(settings.animation);
  296. module.remove.direction();
  297. module.cache = {
  298. className : $module.attr('class'),
  299. style : module.get.style()
  300. };
  301. module.verbose('Saving original attributes', module.cache);
  302. }
  303. },
  304. restore: {
  305. conditions: function() {
  306. if(module.cache === undefined) {
  307. return false;
  308. }
  309. if(module.cache.className) {
  310. $module.attr('class', module.cache.className);
  311. }
  312. else {
  313. $module.removeAttr('class');
  314. }
  315. if(module.cache.style) {
  316. module.verbose('Restoring original style attribute', module.cache.style);
  317. $module.attr('style', module.cache.style);
  318. }
  319. if(module.is.looping()) {
  320. module.remove.looping();
  321. }
  322. module.verbose('Restoring original attributes', module.cache);
  323. }
  324. },
  325. remove: {
  326. animating: function() {
  327. $module.removeClass(className.animating);
  328. },
  329. display: function() {
  330. if(module.displayType !== undefined) {
  331. $module.css('display', '');
  332. }
  333. },
  334. direction: function() {
  335. $module
  336. .removeClass(className.inward)
  337. .removeClass(className.outward)
  338. ;
  339. },
  340. duration: function() {
  341. $module
  342. .css({
  343. '-webkit-animation-duration' : '',
  344. '-moz-animation-duration' : '',
  345. '-ms-animation-duration' : '',
  346. '-o-animation-duration' : '',
  347. 'animation-duration' : ''
  348. })
  349. ;
  350. },
  351. hidden: function() {
  352. $module.removeClass(className.hidden);
  353. },
  354. visible: function() {
  355. $module.removeClass(className.visible);
  356. },
  357. looping: function() {
  358. module.debug('Transitions are no longer looping');
  359. $module
  360. .removeClass(className.looping)
  361. ;
  362. module.forceRepaint();
  363. },
  364. transition: function() {
  365. $module
  366. .removeClass(className.visible)
  367. .removeClass(className.hidden)
  368. ;
  369. }
  370. },
  371. get: {
  372. settings: function(animation, duration, complete) {
  373. // single settings object
  374. if(typeof animation == 'object') {
  375. return $.extend(true, {}, $.fn.transition.settings, animation);
  376. }
  377. // all arguments provided
  378. else if(typeof complete == 'function') {
  379. return $.extend({}, $.fn.transition.settings, {
  380. animation : animation,
  381. complete : complete,
  382. duration : duration
  383. });
  384. }
  385. // only duration provided
  386. else if(typeof duration == 'string' || typeof duration == 'number') {
  387. return $.extend({}, $.fn.transition.settings, {
  388. animation : animation,
  389. duration : duration
  390. });
  391. }
  392. // duration is actually settings object
  393. else if(typeof duration == 'object') {
  394. return $.extend({}, $.fn.transition.settings, duration, {
  395. animation : animation
  396. });
  397. }
  398. // duration is actually callback
  399. else if(typeof duration == 'function') {
  400. return $.extend({}, $.fn.transition.settings, {
  401. animation : animation,
  402. complete : duration
  403. });
  404. }
  405. // only animation provided
  406. else {
  407. return $.extend({}, $.fn.transition.settings, {
  408. animation : animation
  409. });
  410. }
  411. return $.fn.transition.settings;
  412. },
  413. displayType: function() {
  414. if(module.displayType === undefined) {
  415. // create fake element to determine display state
  416. module.can.transition();
  417. }
  418. return module.displayType;
  419. },
  420. style: function() {
  421. var
  422. style = $module.attr('style') || ''
  423. ;
  424. return style.replace(/display.*?;/, '');
  425. },
  426. transitionExists: function(animation) {
  427. return $.fn.transition.exists[animation];
  428. },
  429. animationName: function() {
  430. var
  431. element = document.createElement('div'),
  432. animations = {
  433. 'animation' :'animationName',
  434. 'OAnimation' :'oAnimationName',
  435. 'MozAnimation' :'mozAnimationName',
  436. 'WebkitAnimation' :'webkitAnimationName'
  437. },
  438. animation
  439. ;
  440. for(animation in animations){
  441. if( element.style[animation] !== undefined ){
  442. return animations[animation];
  443. }
  444. }
  445. return false;
  446. },
  447. animationEvent: function() {
  448. var
  449. element = document.createElement('div'),
  450. animations = {
  451. 'animation' :'animationend',
  452. 'OAnimation' :'oAnimationEnd',
  453. 'MozAnimation' :'mozAnimationEnd',
  454. 'WebkitAnimation' :'webkitAnimationEnd'
  455. },
  456. animation
  457. ;
  458. for(animation in animations){
  459. if( element.style[animation] !== undefined ){
  460. return animations[animation];
  461. }
  462. }
  463. return false;
  464. }
  465. },
  466. can: {
  467. animate: function() {
  468. if($module.css(settings.animation) !== 'none') {
  469. module.debug('CSS definition found', $module.css(settings.animation));
  470. return true;
  471. }
  472. else {
  473. module.debug('Unable to find css definition', $module.attr('class'));
  474. return false;
  475. }
  476. },
  477. transition: function() {
  478. var
  479. elementClass = $module.attr('class'),
  480. tagName = $module.prop('tagName'),
  481. animation = settings.animation,
  482. transitionExists = module.get.transitionExists(settings.animation),
  483. $clone,
  484. currentAnimation,
  485. inAnimation,
  486. displayType
  487. ;
  488. if( transitionExists === undefined || module.displayType === undefined) {
  489. module.verbose('Determining whether animation exists');
  490. $clone = $('<' + tagName + ' />').addClass( elementClass ).insertAfter($module);
  491. currentAnimation = $clone
  492. .addClass(animation)
  493. .removeClass(className.inward)
  494. .removeClass(className.outward)
  495. .addClass(className.animating)
  496. .addClass(className.transition)
  497. .css(animationName)
  498. ;
  499. inAnimation = $clone
  500. .addClass(className.inward)
  501. .css(animationName)
  502. ;
  503. displayType = $clone
  504. .attr('class', elementClass)
  505. .removeAttr('style')
  506. .removeClass(className.visible)
  507. .show()
  508. .css('display')
  509. ;
  510. module.verbose('Determining final display state', displayType);
  511. if(currentAnimation != inAnimation) {
  512. module.debug('Transition exists for animation', animation);
  513. transitionExists = true;
  514. }
  515. else {
  516. module.debug('Static animation found', animation, displayType);
  517. transitionExists = false;
  518. }
  519. $clone.remove();
  520. module.save.displayType(displayType);
  521. if(transitionExists === undefined) {
  522. module.save.transitionExists(animation, transitionExists);
  523. }
  524. }
  525. return transitionExists;
  526. }
  527. },
  528. is: {
  529. animating: function() {
  530. return $module.hasClass(className.animating);
  531. },
  532. inward: function() {
  533. return $module.hasClass(className.inward);
  534. },
  535. outward: function() {
  536. return $module.hasClass(className.outward);
  537. },
  538. looping: function() {
  539. return $module.hasClass(className.looping);
  540. },
  541. occuring: function(animation) {
  542. animation = animation || settings.animation;
  543. animation = animation.replace(' ', '.');
  544. return ( $module.filter(animation).size() > 0 );
  545. },
  546. visible: function() {
  547. return $module.is(':visible');
  548. },
  549. hidden: function() {
  550. return $module.css('visibility') === 'hidden';
  551. },
  552. supported: function() {
  553. return(animationName !== false && animationEnd !== false);
  554. }
  555. },
  556. hide: function() {
  557. module.verbose('Hiding element');
  558. module.remove.visible();
  559. module.set.hidden();
  560. module.repaint();
  561. },
  562. show: function(display) {
  563. module.verbose('Showing element', display);
  564. module.remove.hidden();
  565. module.set.visible();
  566. module.repaint();
  567. },
  568. start: function() {
  569. module.verbose('Starting animation');
  570. $module.removeClass(className.disabled);
  571. },
  572. stop: function() {
  573. module.debug('Stopping animation');
  574. $module.addClass(className.disabled);
  575. },
  576. toggle: function() {
  577. module.debug('Toggling play status');
  578. $module.toggleClass(className.disabled);
  579. },
  580. setting: function(name, value) {
  581. module.debug('Changing setting', name, value);
  582. if( $.isPlainObject(name) ) {
  583. $.extend(true, settings, name);
  584. }
  585. else if(value !== undefined) {
  586. settings[name] = value;
  587. }
  588. else {
  589. return settings[name];
  590. }
  591. },
  592. internal: function(name, value) {
  593. if( $.isPlainObject(name) ) {
  594. $.extend(true, module, name);
  595. }
  596. else if(value !== undefined) {
  597. module[name] = value;
  598. }
  599. else {
  600. return module[name];
  601. }
  602. },
  603. debug: function() {
  604. if(settings.debug) {
  605. if(settings.performance) {
  606. module.performance.log(arguments);
  607. }
  608. else {
  609. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  610. module.debug.apply(console, arguments);
  611. }
  612. }
  613. },
  614. verbose: function() {
  615. if(settings.verbose && settings.debug) {
  616. if(settings.performance) {
  617. module.performance.log(arguments);
  618. }
  619. else {
  620. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  621. module.verbose.apply(console, arguments);
  622. }
  623. }
  624. },
  625. error: function() {
  626. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  627. module.error.apply(console, arguments);
  628. },
  629. performance: {
  630. log: function(message) {
  631. var
  632. currentTime,
  633. executionTime,
  634. previousTime
  635. ;
  636. if(settings.performance) {
  637. currentTime = new Date().getTime();
  638. previousTime = time || currentTime;
  639. executionTime = currentTime - previousTime;
  640. time = currentTime;
  641. performance.push({
  642. 'Name' : message[0],
  643. 'Arguments' : [].slice.call(message, 1) || '',
  644. 'Element' : element,
  645. 'Execution Time' : executionTime
  646. });
  647. }
  648. clearTimeout(module.performance.timer);
  649. module.performance.timer = setTimeout(module.performance.display, 600);
  650. },
  651. display: function() {
  652. var
  653. title = settings.name + ':',
  654. totalTime = 0
  655. ;
  656. time = false;
  657. clearTimeout(module.performance.timer);
  658. $.each(performance, function(index, data) {
  659. totalTime += data['Execution Time'];
  660. });
  661. title += ' ' + totalTime + 'ms';
  662. if(moduleSelector) {
  663. title += ' \'' + moduleSelector + '\'';
  664. }
  665. if($allModules.size() > 1) {
  666. title += ' ' + '(' + $allModules.size() + ')';
  667. }
  668. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  669. console.groupCollapsed(title);
  670. if(console.table) {
  671. console.table(performance);
  672. }
  673. else {
  674. $.each(performance, function(index, data) {
  675. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  676. });
  677. }
  678. console.groupEnd();
  679. }
  680. performance = [];
  681. }
  682. },
  683. // modified for transition to return invoke success
  684. invoke: function(query, passedArguments, context) {
  685. var
  686. object = instance,
  687. maxDepth,
  688. found,
  689. response
  690. ;
  691. passedArguments = passedArguments || queryArguments;
  692. context = element || context;
  693. if(typeof query == 'string' && object !== undefined) {
  694. query = query.split(/[\. ]/);
  695. maxDepth = query.length - 1;
  696. $.each(query, function(depth, value) {
  697. var camelCaseValue = (depth != maxDepth)
  698. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  699. : query
  700. ;
  701. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  702. object = object[camelCaseValue];
  703. }
  704. else if( object[camelCaseValue] !== undefined ) {
  705. found = object[camelCaseValue];
  706. return false;
  707. }
  708. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  709. object = object[value];
  710. }
  711. else if( object[value] !== undefined ) {
  712. found = object[value];
  713. return false;
  714. }
  715. else {
  716. return false;
  717. }
  718. });
  719. }
  720. if ( $.isFunction( found ) ) {
  721. response = found.apply(context, passedArguments);
  722. }
  723. else if(found !== undefined) {
  724. response = found;
  725. }
  726. if($.isArray(returnedValue)) {
  727. returnedValue.push(response);
  728. }
  729. else if(returnedValue !== undefined) {
  730. returnedValue = [returnedValue, response];
  731. }
  732. else if(response !== undefined) {
  733. returnedValue = response;
  734. }
  735. return (found !== undefined)
  736. ? found
  737. : false
  738. ;
  739. }
  740. };
  741. module.initialize();
  742. })
  743. ;
  744. return (returnedValue !== undefined)
  745. ? returnedValue
  746. : this
  747. ;
  748. };
  749. // Records if CSS transition is available
  750. $.fn.transition.exists = {};
  751. $.fn.transition.settings = {
  752. // module info
  753. name : 'Transition',
  754. // debug content outputted to console
  755. debug : false,
  756. // verbose debug output
  757. verbose : true,
  758. // performance data output
  759. performance : true,
  760. // event namespace
  761. namespace : 'transition',
  762. // animation complete event
  763. start : function() {},
  764. complete : function() {},
  765. onShow : function() {},
  766. onHide : function() {},
  767. // whether EXACT animation can occur twice in a row
  768. allowRepeats : false,
  769. // animation duration
  770. animation : 'fade',
  771. duration : '500ms',
  772. // new animations will occur after previous ones
  773. queue : true,
  774. className : {
  775. animating : 'animating',
  776. disabled : 'disabled',
  777. hidden : 'hidden',
  778. inward : 'in',
  779. loading : 'loading',
  780. looping : 'looping',
  781. outward : 'out',
  782. transition : 'transition',
  783. visible : 'visible'
  784. },
  785. // possible errors
  786. error: {
  787. noAnimation : 'There is no css animation matching the one you specified.',
  788. repeated : 'That animation is already occurring, cancelling repeated animation',
  789. method : 'The method you called is not defined',
  790. support : 'This browser does not support CSS animations'
  791. }
  792. };
  793. })( jQuery, window , document );