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.

821 lines
25 KiB

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