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

11 years ago
10 years ago
10 years ago
10 years ago
10 years ago
11 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 : '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 );