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.

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