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.

829 lines
26 KiB

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