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.

640 lines
18 KiB

  1. /* *******************************************************************************************
  2. Shape - A 3D Animation Plugin
  3. Version 0.1
  4. (built using Semantic module spec)
  5. Author : Jack Lukic
  6. Last revision : April 2013
  7. ********************************************************************************************* */
  8. ;(function ( $, window, document, undefined ) {
  9. $.fn.shape = function(parameters) {
  10. var
  11. $allModules = $(this),
  12. settings = $.extend(true, {}, $.fn.shape.settings, parameters),
  13. // define namespaces for modules
  14. eventNamespace = '.' + settings.namespace,
  15. moduleNamespace = 'module-' + settings.namespace,
  16. // allow methods to be queried directly
  17. query = arguments[0],
  18. queryArguments = [].slice.call(arguments, 1),
  19. methodInvoked = (typeof query == 'string'),
  20. invokedResponse
  21. ;
  22. $allModules
  23. .each(function() {
  24. var
  25. // selector cache
  26. $module = $(this),
  27. $sides = $module.find(settings.selector.sides),
  28. $side = $module.find(settings.selector.side),
  29. // private variables
  30. $activeSide,
  31. $nextSide,
  32. endTransition = 'transitionend msTransitionEnd oTransitionEnd',
  33. // standard module
  34. selector = $module.selector || '',
  35. element = this,
  36. instance = $module.data(moduleNamespace),
  37. // internal aliases
  38. namespace = settings.namespace,
  39. error = settings.error,
  40. className = settings.className,
  41. module
  42. ;
  43. module = {
  44. initialize: function() {
  45. module.verbose('Initializing module for', element);
  46. module.set.defaultSide();
  47. instance = module;
  48. $module
  49. .data(moduleNamespace, instance)
  50. ;
  51. },
  52. destroy: function() {
  53. module.verbose('Destroying previous module for', element);
  54. $module
  55. .removeData(moduleNamespace)
  56. .off(eventNamespace)
  57. ;
  58. },
  59. refresh: function() {
  60. module.verbose('Refreshing selector cache for', element);
  61. $module = $(element);
  62. $sides = $(this).find(settings.selector.shape);
  63. $side = $(this).find(settings.selector.side);
  64. },
  65. repaint: function() {
  66. module.verbose('Forcing repaint event');
  67. var
  68. shape = $sides.get(0) || document.createElement('div'),
  69. fakeAssignment = shape.offsetWidth
  70. ;
  71. },
  72. animate: function(propertyObject, callback) {
  73. module.verbose('Animating box with properties', propertyObject);
  74. callback = callback || function(event) {
  75. module.reset();
  76. module.set.active();
  77. $.proxy(settings.onChange, $nextSide)();
  78. event.stopImmediatePropagation();
  79. };
  80. if(settings.useCSS) {
  81. module.verbose('Starting CSS animation');
  82. $module
  83. .addClass(className.animating)
  84. ;
  85. module.set.stageSize();
  86. module.repaint();
  87. $module
  88. .addClass(className.css)
  89. ;
  90. $activeSide
  91. .addClass(className.hidden)
  92. ;
  93. $sides
  94. .css(propertyObject)
  95. .one(endTransition, callback)
  96. ;
  97. }
  98. else {
  99. // not yet supported until .animate() is extended to allow RotateX/Y
  100. module.verbose('Starting javascript animation');
  101. $module
  102. .addClass(className.animating)
  103. .removeClass(className.css)
  104. ;
  105. module.set.stageSize();
  106. module.repaint();
  107. $activeSide
  108. .animate({
  109. opacity: 0
  110. }, settings.duration, settings.easing)
  111. ;
  112. $sides
  113. .animate(propertyObject, settings.duration, settings.easing, callback)
  114. ;
  115. }
  116. },
  117. queue: function(method) {
  118. module.debug('Queueing animation of', method);
  119. $sides
  120. .one(endTransition, function() {
  121. module.debug('Executing queued animation');
  122. $module.shape(method);
  123. })
  124. ;
  125. },
  126. reset: function() {
  127. module.verbose('Animating states reset');
  128. $module
  129. .removeClass(className.css)
  130. .removeClass(className.animating)
  131. .removeAttr('style')
  132. ;
  133. $sides
  134. .removeAttr('style')
  135. ;
  136. $side
  137. .removeAttr('style')
  138. .removeClass(className.hidden)
  139. ;
  140. $nextSide
  141. .removeClass(className.animating)
  142. .removeAttr('style')
  143. ;
  144. },
  145. is: {
  146. animating: function() {
  147. return $module.hasClass(className.animating);
  148. }
  149. },
  150. get: {
  151. nextSide: function() {
  152. return ( $activeSide.next(settings.selector.side).size() > 0 )
  153. ? $activeSide.next(settings.selector.side)
  154. : $module.find(settings.selector.side).first()
  155. ;
  156. }
  157. },
  158. set: {
  159. defaultSide: function() {
  160. $activeSide = $module.find('.' + settings.className.active);
  161. $nextSide = ( $activeSide.next(settings.selector.side).size() > 0 )
  162. ? $activeSide.next(settings.selector.side)
  163. : $module.find(settings.selector.side).first()
  164. ;
  165. module.verbose('Active side set to', $activeSide);
  166. module.verbose('Next side set to', $nextSide);
  167. },
  168. stageSize: function() {
  169. var
  170. stage = {
  171. width : $nextSide.outerWidth(),
  172. height : $nextSide.outerHeight()
  173. }
  174. ;
  175. module.verbose('Resizing stage to fit new content', stage);
  176. $module
  177. .css({
  178. width : stage.width,
  179. height : stage.height
  180. })
  181. ;
  182. },
  183. nextSide: function(selector) {
  184. $nextSide = $module.find(selector);
  185. if($nextSide.size() === 0) {
  186. module.error(error.side);
  187. }
  188. module.verbose('Next side manually set to', $nextSide);
  189. },
  190. active: function() {
  191. module.verbose('Setting new side to active', $nextSide);
  192. $side
  193. .removeClass(className.active)
  194. ;
  195. $nextSide
  196. .addClass(className.active)
  197. ;
  198. module.set.defaultSide();
  199. }
  200. },
  201. flip: {
  202. up: function() {
  203. module.debug('Flipping up', $nextSide);
  204. if( !module.is.animating() ) {
  205. module.stage.above();
  206. module.animate( module.getTransform.up() );
  207. }
  208. else {
  209. module.queue('flip.up');
  210. }
  211. },
  212. down: function() {
  213. module.debug('Flipping down', $nextSide);
  214. if( !module.is.animating() ) {
  215. module.stage.below();
  216. module.animate( module.getTransform.down() );
  217. }
  218. else {
  219. module.queue('flip.down');
  220. }
  221. },
  222. left: function() {
  223. module.debug('Flipping left', $nextSide);
  224. if( !module.is.animating() ) {
  225. module.stage.left();
  226. module.animate(module.getTransform.left() );
  227. }
  228. else {
  229. module.queue('flip.left');
  230. }
  231. },
  232. right: function() {
  233. module.debug('Flipping right', $nextSide);
  234. if( !module.is.animating() ) {
  235. module.stage.right();
  236. module.animate(module.getTransform.right() );
  237. }
  238. else {
  239. module.queue('flip.right');
  240. }
  241. },
  242. over: function() {
  243. module.debug('Flipping over', $nextSide);
  244. if( !module.is.animating() ) {
  245. module.stage.behind();
  246. module.animate(module.getTransform.behind() );
  247. }
  248. else {
  249. module.queue('flip.over');
  250. }
  251. }
  252. },
  253. getTransform: {
  254. up: function() {
  255. var
  256. translate = {
  257. y: -(($activeSide.outerHeight() - $nextSide.outerHeight()) / 2),
  258. z: -($activeSide.outerHeight() / 2)
  259. }
  260. ;
  261. return {
  262. transform: 'translateY(' + translate.y + 'px) translateZ('+ translate.z + 'px) rotateX(-90deg)'
  263. };
  264. },
  265. down: function() {
  266. var
  267. translate = {
  268. y: -(($activeSide.outerHeight() - $nextSide.outerHeight()) / 2),
  269. z: -($activeSide.outerHeight() / 2)
  270. }
  271. ;
  272. return {
  273. transform: 'translateY(' + translate.y + 'px) translateZ('+ translate.z + 'px) rotateX(90deg)'
  274. };
  275. },
  276. left: function() {
  277. var
  278. translate = {
  279. x : -(($activeSide.outerWidth() - $nextSide.outerWidth()) / 2),
  280. z : -($activeSide.outerWidth() / 2)
  281. }
  282. ;
  283. return {
  284. transform: 'translateX(' + translate.x + 'px) translateZ(' + translate.z + 'px) rotateY(90deg)'
  285. };
  286. },
  287. right: function() {
  288. var
  289. translate = {
  290. x : -(($activeSide.outerWidth() - $nextSide.outerWidth()) / 2),
  291. z : -($activeSide.outerWidth() / 2)
  292. }
  293. ;
  294. return {
  295. transform: 'translateX(' + translate.x + 'px) translateZ(' + translate.z + 'px) rotateY(-90deg)'
  296. };
  297. },
  298. behind: function() {
  299. var
  300. translate = {
  301. x : -(($activeSide.outerWidth() - $nextSide.outerWidth()) / 2)
  302. }
  303. ;
  304. return {
  305. transform: 'translateX(' + translate.x + 'px) rotateY(180deg)'
  306. };
  307. }
  308. },
  309. stage: {
  310. above: function() {
  311. var
  312. box = {
  313. origin : (($activeSide.outerHeight() - $nextSide.outerHeight()) / 2),
  314. depth : {
  315. active : ($nextSide.outerHeight() / 2),
  316. next : ($activeSide.outerHeight() / 2)
  317. }
  318. }
  319. ;
  320. module.verbose('Setting the initial animation position as above', $nextSide, box);
  321. $activeSide
  322. .css({
  323. 'transform' : 'rotateY(0deg) translateZ(' + box.depth.active + 'px)'
  324. })
  325. ;
  326. $nextSide
  327. .addClass(className.animating)
  328. .css({
  329. 'display' : 'block',
  330. 'top' : box.origin + 'px',
  331. 'transform' : 'rotateX(90deg) translateZ(' + box.depth.next + 'px)'
  332. })
  333. ;
  334. },
  335. below: function() {
  336. var
  337. box = {
  338. origin : (($activeSide.outerHeight() - $nextSide.outerHeight()) / 2),
  339. depth : {
  340. active : ($nextSide.outerHeight() / 2),
  341. next : ($activeSide.outerHeight() / 2)
  342. }
  343. }
  344. ;
  345. module.verbose('Setting the initial animation position as below', $nextSide, box);
  346. $activeSide
  347. .css({
  348. 'transform' : 'rotateY(0deg) translateZ(' + box.depth.active + 'px)'
  349. })
  350. ;
  351. $nextSide
  352. .addClass(className.animating)
  353. .css({
  354. 'display' : 'block',
  355. 'top' : box.origin + 'px',
  356. 'transform' : 'rotateX(-90deg) translateZ(' + box.depth.next + 'px)'
  357. })
  358. ;
  359. },
  360. left: function() {
  361. var
  362. box = {
  363. origin : ( ( $activeSide.outerWidth() - $nextSide.outerWidth() ) / 2),
  364. depth : {
  365. active : ($nextSide.outerWidth() / 2),
  366. next : ($activeSide.outerWidth() / 2)
  367. }
  368. }
  369. ;
  370. module.verbose('Setting the initial animation position as left', $nextSide, box);
  371. $activeSide
  372. .css({
  373. 'transform' : 'rotateY(0deg) translateZ(' + box.depth.active + 'px)'
  374. })
  375. ;
  376. $nextSide
  377. .addClass(className.animating)
  378. .css({
  379. 'display' : 'block',
  380. 'left' : box.origin + 'px',
  381. 'transform' : 'rotateY(-90deg) translateZ(' + box.depth.next + 'px)'
  382. })
  383. ;
  384. },
  385. right: function() {
  386. var
  387. box = {
  388. origin : ( ( $activeSide.outerWidth() - $nextSide.outerWidth() ) / 2),
  389. depth : {
  390. active : ($nextSide.outerWidth() / 2),
  391. next : ($activeSide.outerWidth() / 2)
  392. }
  393. }
  394. ;
  395. module.verbose('Setting the initial animation position as left', $nextSide, box);
  396. $activeSide
  397. .css({
  398. 'transform' : 'rotateY(0deg) translateZ(' + box.depth.active + 'px)'
  399. })
  400. ;
  401. $nextSide
  402. .addClass(className.animating)
  403. .css({
  404. 'display' : 'block',
  405. 'left' : box.origin + 'px',
  406. 'transform' : 'rotateY(90deg) translateZ(' + box.depth.next + 'px)'
  407. })
  408. ;
  409. },
  410. behind: function() {
  411. var
  412. box = {
  413. origin : ( ( $activeSide.outerWidth() - $nextSide.outerWidth() ) / 2),
  414. depth : {
  415. active : ($nextSide.outerWidth() / 2),
  416. next : ($activeSide.outerWidth() / 2)
  417. }
  418. }
  419. ;
  420. module.verbose('Setting the initial animation position as behind', $nextSide, box);
  421. $activeSide
  422. .css({
  423. 'transform' : 'rotateY(0deg)'
  424. })
  425. ;
  426. $nextSide
  427. .addClass(className.animating)
  428. .css({
  429. 'display' : 'block',
  430. 'left' : box.origin + 'px',
  431. 'transform' : 'rotateY(-180deg)'
  432. })
  433. ;
  434. }
  435. },
  436. /* standard module */
  437. setting: function(name, value) {
  438. if( $.isPlainObject(name) ) {
  439. $.extend(true, settings, name);
  440. }
  441. else if(value === undefined) {
  442. return settings[name];
  443. }
  444. else {
  445. settings[name] = value;
  446. }
  447. },
  448. verbose: function() {
  449. if(settings.verbose) {
  450. module.debug.apply(this, arguments);
  451. }
  452. },
  453. debug: function() {
  454. var
  455. output = [],
  456. message = settings.moduleName + ': ' + arguments[0],
  457. variables = [].slice.call( arguments, 1 ),
  458. log = console.info || console.log || function(){}
  459. ;
  460. log = Function.prototype.bind.call(log, console);
  461. if(settings.debug) {
  462. output.push(message);
  463. log.apply(console, output.concat(variables) );
  464. }
  465. },
  466. error: function() {
  467. var
  468. output = [],
  469. errorMessage = settings.moduleName + ': ' + arguments[0],
  470. variables = [].slice.call( arguments, 1 ),
  471. log = console.warn || console.log || function(){}
  472. ;
  473. log = Function.prototype.bind.call(log, console);
  474. if(settings.debug) {
  475. output.push(errorMessage);
  476. output.concat(variables);
  477. log.apply(console, output.concat(variables) );
  478. }
  479. },
  480. invoke: function(query, passedArguments, context) {
  481. var
  482. maxDepth,
  483. found
  484. ;
  485. passedArguments = passedArguments || queryArguments || [].slice.call( arguments, 2 );
  486. context = element || context;
  487. if(typeof query == 'string' && instance !== undefined) {
  488. query = query.split('.');
  489. maxDepth = query.length - 1;
  490. $.each(query, function(depth, value) {
  491. if( $.isPlainObject( instance[value] ) && (depth != maxDepth) ) {
  492. instance = instance[value];
  493. return true;
  494. }
  495. else if( instance[value] !== undefined ) {
  496. found = instance[value];
  497. return true;
  498. }
  499. module.error(error.method);
  500. return false;
  501. });
  502. }
  503. if ( $.isFunction( found ) ) {
  504. module.verbose('Executing invoked function', found);
  505. return found.apply(context, passedArguments);
  506. }
  507. // return retrieved variable or chain
  508. return found || false;
  509. }
  510. };
  511. // check for invoking internal method
  512. if(methodInvoked) {
  513. if(instance === undefined) {
  514. module.initialize();
  515. }
  516. invokedResponse = module.invoke(query);
  517. }
  518. // otherwise initialize
  519. else {
  520. if(instance !== undefined) {
  521. module.destroy();
  522. }
  523. module.initialize();
  524. }
  525. })
  526. ;
  527. // chain or return queried method
  528. return (invokedResponse)
  529. ? invokedResponse
  530. : this
  531. ;
  532. };
  533. $.fn.shape.settings = {
  534. // module info
  535. moduleName : 'Shape Module',
  536. // debug content outputted to console
  537. debug : true,
  538. // verbose debug output
  539. verbose : true,
  540. // event namespace
  541. namespace : 'shape',
  542. // callback occurs on side change
  543. beforeChange : function() {},
  544. onChange : function() {},
  545. // use css animation (currently only true is supported)
  546. useCSS : true,
  547. // animation duration (useful only with future js animations)
  548. duration : 1000,
  549. easing : 'easeInOutQuad',
  550. // possible errors
  551. error: {
  552. side : 'You tried to switch to a side that does not exist.',
  553. method : 'The method you called is not defined'
  554. },
  555. // classnames used
  556. className : {
  557. css : 'css',
  558. animating : 'animating',
  559. hidden : 'hidden',
  560. active : 'active'
  561. },
  562. // selectors used
  563. selector : {
  564. sides : '.sides',
  565. side : '.side'
  566. }
  567. };
  568. })( jQuery, window , document );