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

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