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.

839 lines
26 KiB

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