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.

813 lines
25 KiB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
  1. /*
  2. * # Semantic - Transition
  3. * http://github.com/jlukic/semantic-ui/
  4. *
  5. *
  6. * Copyright 2013 Contributors
  7. * Released under the MIT license
  8. * http://opensource.org/licenses/MIT
  9. *
  10. */
  11. ;(function ( $, window, document, undefined ) {
  12. $.fn.transition = function() {
  13. var
  14. $allModules = $(this),
  15. moduleSelector = $allModules.selector || '',
  16. time = new Date().getTime(),
  17. performance = [],
  18. moduleArguments = arguments,
  19. query = moduleArguments[0],
  20. queryArguments = [].slice.call(arguments, 1),
  21. methodInvoked = (typeof query === 'string'),
  22. requestAnimationFrame = window.requestAnimationFrame
  23. || window.mozRequestAnimationFrame
  24. || window.webkitRequestAnimationFrame
  25. || window.msRequestAnimationFrame
  26. || function(callback) { setTimeout(callback, 0); },
  27. 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. }
  67. module.instantiate();
  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. displayType = module.get.displayType()
  200. ;
  201. if(displayType !== 'block') {
  202. module.verbose('Setting final visibility to', displayType);
  203. $module
  204. .css({
  205. display: displayType
  206. })
  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. },
  255. visible: function() {
  256. $module
  257. .addClass(className.transition)
  258. .addClass(className.visible)
  259. ;
  260. }
  261. },
  262. save: {
  263. displayType: function(displayType) {
  264. instance.displayType = displayType;
  265. },
  266. transitionExists: function(animation, exists) {
  267. $.fn.transition.exists[animation] = exists;
  268. module.verbose('Saving existence of transition', animation, exists);
  269. },
  270. conditions: function() {
  271. instance.cache = {
  272. className : $module.attr('class'),
  273. style : $module.attr('style')
  274. };
  275. module.verbose('Saving original attributes', instance.cache);
  276. }
  277. },
  278. restore: {
  279. conditions: function() {
  280. if(instance.cache === undefined) {
  281. return false;
  282. }
  283. if(instance.cache.className) {
  284. $module.attr('class', instance.cache.className);
  285. }
  286. else {
  287. $module.removeAttr('class');
  288. }
  289. if(instance.cache.style) {
  290. $module.attr('style', instance.cache.style);
  291. }
  292. else {
  293. if(module.get.displayType() === 'block') {
  294. $module.removeAttr('style');
  295. }
  296. }
  297. if(module.is.looping()) {
  298. module.remove.looping();
  299. }
  300. module.verbose('Restoring original attributes', instance.cache);
  301. }
  302. },
  303. remove: {
  304. animating: function() {
  305. $module.removeClass(className.animating);
  306. },
  307. display: function() {
  308. if(instance.displayType !== undefined) {
  309. $module.css('display', '');
  310. }
  311. },
  312. duration: function() {
  313. $module
  314. .css({
  315. '-webkit-animation-duration' : '',
  316. '-moz-animation-duration' : '',
  317. '-ms-animation-duration' : '',
  318. '-o-animation-duration' : '',
  319. 'animation-duration' : ''
  320. })
  321. ;
  322. },
  323. hidden: function() {
  324. $module.removeClass(className.hidden);
  325. },
  326. visible: function() {
  327. $module.removeClass(className.visible);
  328. },
  329. looping: function() {
  330. module.debug('Transitions are no longer looping');
  331. $module
  332. .removeClass(className.looping)
  333. ;
  334. module.forceRepaint();
  335. }
  336. },
  337. get: {
  338. settings: function(animation, duration, complete) {
  339. // single settings object
  340. if(typeof animation == 'object') {
  341. return $.extend(true, {}, $.fn.transition.settings, animation);
  342. }
  343. // all arguments provided
  344. else if(typeof complete == 'function') {
  345. return $.extend({}, $.fn.transition.settings, {
  346. animation : animation,
  347. complete : complete,
  348. duration : duration
  349. });
  350. }
  351. // only duration provided
  352. else if(typeof duration == 'string' || typeof duration == 'number') {
  353. return $.extend({}, $.fn.transition.settings, {
  354. animation : animation,
  355. duration : duration
  356. });
  357. }
  358. // duration is actually settings object
  359. else if(typeof duration == 'object') {
  360. return $.extend({}, $.fn.transition.settings, duration, {
  361. animation : animation
  362. });
  363. }
  364. // duration is actually callback
  365. else if(typeof duration == 'function') {
  366. return $.extend({}, $.fn.transition.settings, {
  367. animation : animation,
  368. complete : duration
  369. });
  370. }
  371. // only animation provided
  372. else {
  373. return $.extend({}, $.fn.transition.settings, {
  374. animation : animation
  375. });
  376. }
  377. return $.fn.transition.settings;
  378. },
  379. displayType: function() {
  380. if(instance.displayType === undefined) {
  381. // create fake element to determine display state
  382. module.can.transition();
  383. }
  384. return instance.displayType;
  385. },
  386. transitionExists: function(animation) {
  387. return $.fn.transition.exists[animation];
  388. },
  389. animationName: function() {
  390. var
  391. element = document.createElement('div'),
  392. animations = {
  393. 'animation' :'animationName',
  394. 'OAnimation' :'oAnimationName',
  395. 'MozAnimation' :'mozAnimationName',
  396. 'WebkitAnimation' :'webkitAnimationName'
  397. },
  398. animation
  399. ;
  400. for(animation in animations){
  401. if( element.style[animation] !== undefined ){
  402. module.verbose('Determined animation vendor name property', animations[animation]);
  403. return animations[animation];
  404. }
  405. }
  406. return false;
  407. },
  408. animationEvent: function() {
  409. var
  410. element = document.createElement('div'),
  411. animations = {
  412. 'animation' :'animationend',
  413. 'OAnimation' :'oAnimationEnd',
  414. 'MozAnimation' :'mozAnimationEnd',
  415. 'WebkitAnimation' :'webkitAnimationEnd'
  416. },
  417. animation
  418. ;
  419. for(animation in animations){
  420. if( element.style[animation] !== undefined ){
  421. module.verbose('Determined animation vendor end event', animations[animation]);
  422. return animations[animation];
  423. }
  424. }
  425. return false;
  426. }
  427. },
  428. can: {
  429. animate: function() {
  430. if($module.css(settings.animation) !== 'none') {
  431. module.debug('CSS definition found', $module.css(settings.animation));
  432. return true;
  433. }
  434. else {
  435. module.debug('Unable to find css definition', $module.attr('class'));
  436. return false;
  437. }
  438. },
  439. transition: function() {
  440. var
  441. elementClass = $module.attr('class'),
  442. animation = settings.animation,
  443. transitionExists = module.get.transitionExists(settings.animation),
  444. $clone,
  445. currentAnimation,
  446. inAnimation,
  447. displayType
  448. ;
  449. if( transitionExists === undefined || instance.displayType === undefined) {
  450. module.verbose('Determining whether animation exists');
  451. $clone = $('<div>').addClass( elementClass ).appendTo($('body'));
  452. currentAnimation = $clone
  453. .removeClass(className.inward)
  454. .removeClass(className.outward)
  455. .addClass(className.animating)
  456. .addClass(className.transition)
  457. .addClass(animation)
  458. .css(animationName)
  459. ;
  460. inAnimation = $clone
  461. .addClass(className.inward)
  462. .css(animationName)
  463. ;
  464. displayType = $clone
  465. .attr('class', elementClass)
  466. .show()
  467. .css('display')
  468. ;
  469. module.verbose('Determining final display state', displayType);
  470. if(currentAnimation != inAnimation) {
  471. module.debug('Transition exists for animation', animation);
  472. transitionExists = true;
  473. }
  474. else {
  475. module.debug('Static animation found', animation, displayType);
  476. transitionExists = false;
  477. }
  478. $clone.remove();
  479. module.save.displayType(displayType);
  480. module.save.transitionExists(animation, transitionExists);
  481. }
  482. return transitionExists;
  483. }
  484. },
  485. is: {
  486. animating: function() {
  487. return $module.hasClass(className.animating);
  488. },
  489. inward: function() {
  490. return $module.hasClass(className.inward);
  491. },
  492. outward: function() {
  493. return $module.hasClass(className.outward);
  494. },
  495. looping: function() {
  496. return $module.hasClass(className.looping);
  497. },
  498. occuring: function(animation) {
  499. animation = animation || settings.animation;
  500. return ( $module.hasClass(animation) );
  501. },
  502. visible: function() {
  503. return $module.is(':visible');
  504. },
  505. supported: function() {
  506. return(animationName !== false && animationEnd !== false);
  507. }
  508. },
  509. hide: function() {
  510. module.verbose('Hiding element');
  511. module.remove.visible();
  512. module.set.hidden();
  513. module.repaint();
  514. },
  515. show: function(display) {
  516. module.verbose('Showing element', display);
  517. module.remove.hidden();
  518. module.set.visible();
  519. module.repaint();
  520. },
  521. start: function() {
  522. module.verbose('Starting animation');
  523. $module.removeClass(className.disabled);
  524. },
  525. stop: function() {
  526. module.debug('Stopping animation');
  527. $module.addClass(className.disabled);
  528. },
  529. toggle: function() {
  530. module.debug('Toggling play status');
  531. $module.toggleClass(className.disabled);
  532. },
  533. setting: function(name, value) {
  534. if( $.isPlainObject(name) ) {
  535. $.extend(true, settings, name);
  536. }
  537. else if(value !== undefined) {
  538. settings[name] = value;
  539. }
  540. else {
  541. return settings[name];
  542. }
  543. },
  544. internal: function(name, value) {
  545. if( $.isPlainObject(name) ) {
  546. $.extend(true, module, name);
  547. }
  548. else if(value !== undefined) {
  549. module[name] = value;
  550. }
  551. else {
  552. return module[name];
  553. }
  554. },
  555. debug: function() {
  556. if(settings.debug) {
  557. if(settings.performance) {
  558. module.performance.log(arguments);
  559. }
  560. else {
  561. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  562. module.debug.apply(console, arguments);
  563. }
  564. }
  565. },
  566. verbose: function() {
  567. if(settings.verbose && settings.debug) {
  568. if(settings.performance) {
  569. module.performance.log(arguments);
  570. }
  571. else {
  572. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  573. module.verbose.apply(console, arguments);
  574. }
  575. }
  576. },
  577. error: function() {
  578. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  579. module.error.apply(console, arguments);
  580. },
  581. performance: {
  582. log: function(message) {
  583. var
  584. currentTime,
  585. executionTime,
  586. previousTime
  587. ;
  588. if(settings.performance) {
  589. currentTime = new Date().getTime();
  590. previousTime = time || currentTime;
  591. executionTime = currentTime - previousTime;
  592. time = currentTime;
  593. performance.push({
  594. 'Element' : element,
  595. 'Name' : message[0],
  596. 'Arguments' : [].slice.call(message, 1) || '',
  597. 'Execution Time' : executionTime
  598. });
  599. }
  600. clearTimeout(module.performance.timer);
  601. module.performance.timer = setTimeout(module.performance.display, 100);
  602. },
  603. display: function() {
  604. var
  605. title = settings.name + ':',
  606. totalTime = 0
  607. ;
  608. time = false;
  609. clearTimeout(module.performance.timer);
  610. $.each(performance, function(index, data) {
  611. totalTime += data['Execution Time'];
  612. });
  613. title += ' ' + totalTime + 'ms';
  614. if(moduleSelector) {
  615. title += ' \'' + moduleSelector + '\'';
  616. }
  617. if($allModules.size() > 1) {
  618. title += ' ' + '(' + $allModules.size() + ')';
  619. }
  620. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  621. console.groupCollapsed(title);
  622. if(console.table) {
  623. console.table(performance);
  624. }
  625. else {
  626. $.each(performance, function(index, data) {
  627. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  628. });
  629. }
  630. console.groupEnd();
  631. }
  632. performance = [];
  633. }
  634. },
  635. invoke: function(query, passedArguments, context) {
  636. var
  637. object = instance,
  638. maxDepth,
  639. found,
  640. response
  641. ;
  642. passedArguments = passedArguments || queryArguments;
  643. context = element || context;
  644. if(typeof query == 'string' && object !== undefined) {
  645. query = query.split(/[\. ]/);
  646. maxDepth = query.length - 1;
  647. $.each(query, function(depth, value) {
  648. var camelCaseValue = (depth != maxDepth)
  649. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  650. : query
  651. ;
  652. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  653. object = object[camelCaseValue];
  654. }
  655. else if( object[camelCaseValue] !== undefined ) {
  656. found = object[camelCaseValue];
  657. return false;
  658. }
  659. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  660. object = object[value];
  661. }
  662. else if( object[value] !== undefined ) {
  663. found = object[value];
  664. return false;
  665. }
  666. else {
  667. return false;
  668. }
  669. });
  670. }
  671. if ( $.isFunction( found ) ) {
  672. response = found.apply(context, passedArguments);
  673. }
  674. else if(found !== undefined) {
  675. response = found;
  676. }
  677. if($.isArray(returnedValue)) {
  678. returnedValue.push(response);
  679. }
  680. else if(returnedValue !== undefined) {
  681. returnedValue = [returnedValue, response];
  682. }
  683. else if(response !== undefined) {
  684. returnedValue = response;
  685. }
  686. return found || false;
  687. }
  688. };
  689. module.initialize();
  690. })
  691. ;
  692. return (returnedValue !== undefined)
  693. ? returnedValue
  694. : this
  695. ;
  696. };
  697. $.fn.transition.exists = {};
  698. $.fn.transition.settings = {
  699. // module info
  700. name : 'Transition',
  701. // debug content outputted to console
  702. debug : true,
  703. // verbose debug output
  704. verbose : true,
  705. // performance data output
  706. performance : true,
  707. // event namespace
  708. namespace : 'transition',
  709. // animation complete event
  710. complete : function() {},
  711. onShow : function() {},
  712. onHide : function() {},
  713. // whether animation can occur twice in a row
  714. allowRepeats : false,
  715. // animation duration
  716. animation : 'fade',
  717. duration : '700ms',
  718. // new animations will occur after previous ones
  719. queue : true,
  720. className : {
  721. animating : 'animating',
  722. disabled : 'disabled',
  723. hidden : 'hidden',
  724. inward : 'in',
  725. loading : 'loading',
  726. looping : 'looping',
  727. outward : 'out',
  728. transition : 'ui transition',
  729. visible : 'visible'
  730. },
  731. // possible errors
  732. error: {
  733. noAnimation : 'There is no css animation matching the one you specified.',
  734. repeated : 'That animation is already occurring, cancelling repeated animation',
  735. method : 'The method you called is not defined',
  736. support : 'This browser does not support CSS animations'
  737. }
  738. };
  739. })( jQuery, window , document );