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

10 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. module.exports = 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. module.exports.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, {}, module.exports.settings, animation);
  342. }
  343. // all arguments provided
  344. else if(typeof complete == 'function') {
  345. return $.extend({}, module.exports.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({}, module.exports.settings, {
  354. animation : animation,
  355. duration : duration
  356. });
  357. }
  358. // duration is actually settings object
  359. else if(typeof duration == 'object') {
  360. return $.extend({}, module.exports.settings, duration, {
  361. animation : animation
  362. });
  363. }
  364. // duration is actually callback
  365. else if(typeof duration == 'function') {
  366. return $.extend({}, module.exports.settings, {
  367. animation : animation,
  368. complete : duration
  369. });
  370. }
  371. // only animation provided
  372. else {
  373. return $.extend({}, module.exports.settings, {
  374. animation : animation
  375. });
  376. }
  377. return module.exports.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 module.exports.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' :'animationend',
  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. module.exports.exists = {};
  698. module.exports.settings = {
  699. // module info
  700. name : 'Transition',
  701. // debug content outputted to console
  702. debug : false,
  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. })( require("jquery"), window , document );