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.

747 lines
22 KiB

  1. /* ******************************
  2. Semantic Module: Shape
  3. Author: Jack Lukic
  4. Notes: First Commit March 25, 2013
  5. An experimental plugin for manipulating 3D shapes on a 2D plane
  6. ****************************** */
  7. ;(function ( $, window, document, undefined ) {
  8. $.fn.shape = function(parameters) {
  9. var
  10. $allModules = $(this),
  11. settings = $.extend(true, {}, $.fn.shape.settings, parameters),
  12. // define namespaces for modules
  13. eventNamespace = '.' + settings.namespace,
  14. moduleNamespace = 'module-' + settings.namespace,
  15. moduleSelector = $allModules.selector || '',
  16. time = new Date().getTime(),
  17. performance = [],
  18. query = arguments[0],
  19. methodInvoked = (typeof query == 'string'),
  20. queryArguments = [].slice.call(arguments, 1),
  21. invokedResponse
  22. ;
  23. $allModules
  24. .each(function() {
  25. var
  26. // selector cache
  27. $module = $(this),
  28. $sides = $module.find(settings.selector.sides),
  29. $side = $module.find(settings.selector.side),
  30. // private variables
  31. $activeSide,
  32. $nextSide,
  33. // standard module
  34. element = this,
  35. instance = $module.data(moduleNamespace),
  36. // internal aliases
  37. namespace = settings.namespace,
  38. error = settings.error,
  39. className = settings.className,
  40. module
  41. ;
  42. module = {
  43. initialize: function() {
  44. module.verbose('Initializing module for', element);
  45. module.set.defaultSide();
  46. instance = module;
  47. $module
  48. .data(moduleNamespace, instance)
  49. ;
  50. },
  51. destroy: function() {
  52. module.verbose('Destroying previous module for', element);
  53. $module
  54. .removeData(moduleNamespace)
  55. .off(eventNamespace)
  56. ;
  57. },
  58. refresh: function() {
  59. module.verbose('Refreshing selector cache for', element);
  60. $module = $(element);
  61. $sides = $(this).find(settings.selector.shape);
  62. $side = $(this).find(settings.selector.side);
  63. },
  64. repaint: function() {
  65. module.verbose('Forcing repaint event');
  66. var
  67. shape = $sides.get(0) || document.createElement('div'),
  68. fakeAssignment = shape.offsetWidth
  69. ;
  70. },
  71. animate: function(propertyObject, callback) {
  72. module.verbose('Animating box with properties', propertyObject);
  73. callback = callback || function(event) {
  74. module.verbose('Executing animation callback', event);
  75. event.stopPropagation();
  76. module.reset();
  77. module.set.active();
  78. $.proxy(settings.onChange, $nextSide)();
  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(module.get.transitionEvent(), 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(module.get.transitionEvent(), function() {
  121. module.debug('Executing queued animation');
  122. setTimeout(function(){
  123. $module.shape(method);
  124. }, 0);
  125. })
  126. ;
  127. },
  128. reset: function() {
  129. module.verbose('Animating states reset');
  130. $module
  131. .removeClass(className.css)
  132. .removeClass(className.animating)
  133. .removeAttr('style')
  134. ;
  135. $sides
  136. .removeAttr('style')
  137. ;
  138. $side
  139. .removeAttr('style')
  140. .removeClass(className.hidden)
  141. ;
  142. $nextSide
  143. .removeClass(className.animating)
  144. .removeAttr('style')
  145. ;
  146. },
  147. is: {
  148. animating: function() {
  149. return $module.hasClass(className.animating);
  150. }
  151. },
  152. get: {
  153. transitionEvent: function() {
  154. var
  155. element = document.createElement('element'),
  156. transitions = {
  157. 'transition' :'transitionend',
  158. 'OTransition' :'oTransitionEnd',
  159. 'MozTransition' :'transitionend',
  160. 'WebkitTransition' :'webkitTransitionEnd'
  161. },
  162. transition
  163. ;
  164. for(transition in transitions){
  165. if( element.style[transition] !== undefined ){
  166. return transitions[transition];
  167. }
  168. }
  169. },
  170. nextSide: function() {
  171. return ( $activeSide.next(settings.selector.side).size() > 0 )
  172. ? $activeSide.next(settings.selector.side)
  173. : $module.find(settings.selector.side).first()
  174. ;
  175. }
  176. },
  177. set: {
  178. defaultSide: function() {
  179. $activeSide = $module.find('.' + settings.className.active);
  180. $nextSide = ( $activeSide.next(settings.selector.side).size() > 0 )
  181. ? $activeSide.next(settings.selector.side)
  182. : $module.find(settings.selector.side).first()
  183. ;
  184. module.verbose('Active side set to', $activeSide);
  185. module.verbose('Next side set to', $nextSide);
  186. },
  187. stageSize: function() {
  188. var
  189. stage = {
  190. width : $nextSide.outerWidth(),
  191. height : $nextSide.outerHeight()
  192. }
  193. ;
  194. module.verbose('Resizing stage to fit new content', stage);
  195. $module
  196. .css({
  197. width : stage.width,
  198. height : stage.height
  199. })
  200. ;
  201. },
  202. nextSide: function(selector) {
  203. $nextSide = $module.find(selector);
  204. if($nextSide.size() === 0) {
  205. module.error(error.side);
  206. }
  207. module.verbose('Next side manually set to', $nextSide);
  208. },
  209. active: function() {
  210. module.verbose('Setting new side to active', $nextSide);
  211. $side
  212. .removeClass(className.active)
  213. ;
  214. $nextSide
  215. .addClass(className.active)
  216. ;
  217. module.set.defaultSide();
  218. }
  219. },
  220. flip: {
  221. up: function() {
  222. module.debug('Flipping up', $nextSide);
  223. if( !module.is.animating() ) {
  224. module.stage.above();
  225. module.animate( module.getTransform.up() );
  226. }
  227. else {
  228. module.queue('flip.up');
  229. }
  230. },
  231. down: function() {
  232. module.debug('Flipping down', $nextSide);
  233. if( !module.is.animating() ) {
  234. module.stage.below();
  235. module.animate( module.getTransform.down() );
  236. }
  237. else {
  238. module.queue('flip.down');
  239. }
  240. },
  241. left: function() {
  242. module.debug('Flipping left', $nextSide);
  243. if( !module.is.animating() ) {
  244. module.stage.left();
  245. module.animate(module.getTransform.left() );
  246. }
  247. else {
  248. module.queue('flip.left');
  249. }
  250. },
  251. right: function() {
  252. module.debug('Flipping right', $nextSide);
  253. if( !module.is.animating() ) {
  254. module.stage.right();
  255. module.animate(module.getTransform.right() );
  256. }
  257. else {
  258. module.queue('flip.right');
  259. }
  260. },
  261. over: function() {
  262. module.debug('Flipping over', $nextSide);
  263. if( !module.is.animating() ) {
  264. module.stage.behind();
  265. module.animate(module.getTransform.over() );
  266. }
  267. else {
  268. module.queue('flip.over');
  269. }
  270. },
  271. back: function() {
  272. module.debug('Flipping back', $nextSide);
  273. if( !module.is.animating() ) {
  274. module.stage.behind();
  275. module.animate(module.getTransform.back() );
  276. }
  277. else {
  278. module.queue('flip.back');
  279. }
  280. }
  281. },
  282. getTransform: {
  283. up: function() {
  284. var
  285. translate = {
  286. y: -(($activeSide.outerHeight() - $nextSide.outerHeight()) / 2),
  287. z: -($activeSide.outerHeight() / 2)
  288. }
  289. ;
  290. return {
  291. transform: 'translateY(' + translate.y + 'px) translateZ('+ translate.z + 'px) rotateX(-90deg)'
  292. };
  293. },
  294. down: function() {
  295. var
  296. translate = {
  297. y: -(($activeSide.outerHeight() - $nextSide.outerHeight()) / 2),
  298. z: -($activeSide.outerHeight() / 2)
  299. }
  300. ;
  301. return {
  302. transform: 'translateY(' + translate.y + 'px) translateZ('+ translate.z + 'px) rotateX(90deg)'
  303. };
  304. },
  305. left: function() {
  306. var
  307. translate = {
  308. x : -(($activeSide.outerWidth() - $nextSide.outerWidth()) / 2),
  309. z : -($activeSide.outerWidth() / 2)
  310. }
  311. ;
  312. return {
  313. transform: 'translateX(' + translate.x + 'px) translateZ(' + translate.z + 'px) rotateY(90deg)'
  314. };
  315. },
  316. right: function() {
  317. var
  318. translate = {
  319. x : -(($activeSide.outerWidth() - $nextSide.outerWidth()) / 2),
  320. z : -($activeSide.outerWidth() / 2)
  321. }
  322. ;
  323. return {
  324. transform: 'translateX(' + translate.x + 'px) translateZ(' + translate.z + 'px) rotateY(-90deg)'
  325. };
  326. },
  327. over: function() {
  328. var
  329. translate = {
  330. x : -(($activeSide.outerWidth() - $nextSide.outerWidth()) / 2)
  331. }
  332. ;
  333. return {
  334. transform: 'translateX(' + translate.x + 'px) rotateY(180deg)'
  335. };
  336. },
  337. back: function() {
  338. var
  339. translate = {
  340. x : -(($activeSide.outerWidth() - $nextSide.outerWidth()) / 2)
  341. }
  342. ;
  343. return {
  344. transform: 'translateX(' + translate.x + 'px) rotateY(-180deg)'
  345. };
  346. }
  347. },
  348. stage: {
  349. above: function() {
  350. var
  351. box = {
  352. origin : (($activeSide.outerHeight() - $nextSide.outerHeight()) / 2),
  353. depth : {
  354. active : ($nextSide.outerHeight() / 2),
  355. next : ($activeSide.outerHeight() / 2)
  356. }
  357. }
  358. ;
  359. module.verbose('Setting the initial animation position as above', $nextSide, box);
  360. $activeSide
  361. .css({
  362. 'transform' : 'rotateY(0deg) translateZ(' + box.depth.active + 'px)'
  363. })
  364. ;
  365. $nextSide
  366. .addClass(className.animating)
  367. .css({
  368. 'display' : 'block',
  369. 'top' : box.origin + 'px',
  370. 'transform' : 'rotateX(90deg) translateZ(' + box.depth.next + 'px)'
  371. })
  372. ;
  373. },
  374. below: function() {
  375. var
  376. box = {
  377. origin : (($activeSide.outerHeight() - $nextSide.outerHeight()) / 2),
  378. depth : {
  379. active : ($nextSide.outerHeight() / 2),
  380. next : ($activeSide.outerHeight() / 2)
  381. }
  382. }
  383. ;
  384. module.verbose('Setting the initial animation position as below', $nextSide, box);
  385. $activeSide
  386. .css({
  387. 'transform' : 'rotateY(0deg) translateZ(' + box.depth.active + 'px)'
  388. })
  389. ;
  390. $nextSide
  391. .addClass(className.animating)
  392. .css({
  393. 'display' : 'block',
  394. 'top' : box.origin + 'px',
  395. 'transform' : 'rotateX(-90deg) translateZ(' + box.depth.next + 'px)'
  396. })
  397. ;
  398. },
  399. left: function() {
  400. var
  401. box = {
  402. origin : ( ( $activeSide.outerWidth() - $nextSide.outerWidth() ) / 2),
  403. depth : {
  404. active : ($nextSide.outerWidth() / 2),
  405. next : ($activeSide.outerWidth() / 2)
  406. }
  407. }
  408. ;
  409. module.verbose('Setting the initial animation position as left', $nextSide, box);
  410. $activeSide
  411. .css({
  412. 'transform' : 'rotateY(0deg) translateZ(' + box.depth.active + 'px)'
  413. })
  414. ;
  415. $nextSide
  416. .addClass(className.animating)
  417. .css({
  418. 'display' : 'block',
  419. 'left' : box.origin + 'px',
  420. 'transform' : 'rotateY(-90deg) translateZ(' + box.depth.next + 'px)'
  421. })
  422. ;
  423. },
  424. right: function() {
  425. var
  426. box = {
  427. origin : ( ( $activeSide.outerWidth() - $nextSide.outerWidth() ) / 2),
  428. depth : {
  429. active : ($nextSide.outerWidth() / 2),
  430. next : ($activeSide.outerWidth() / 2)
  431. }
  432. }
  433. ;
  434. module.verbose('Setting the initial animation position as left', $nextSide, box);
  435. $activeSide
  436. .css({
  437. 'transform' : 'rotateY(0deg) translateZ(' + box.depth.active + 'px)'
  438. })
  439. ;
  440. $nextSide
  441. .addClass(className.animating)
  442. .css({
  443. 'display' : 'block',
  444. 'left' : box.origin + 'px',
  445. 'transform' : 'rotateY(90deg) translateZ(' + box.depth.next + 'px)'
  446. })
  447. ;
  448. },
  449. behind: function() {
  450. var
  451. box = {
  452. origin : ( ( $activeSide.outerWidth() - $nextSide.outerWidth() ) / 2),
  453. depth : {
  454. active : ($nextSide.outerWidth() / 2),
  455. next : ($activeSide.outerWidth() / 2)
  456. }
  457. }
  458. ;
  459. module.verbose('Setting the initial animation position as behind', $nextSide, box);
  460. $activeSide
  461. .css({
  462. 'transform' : 'rotateY(0deg)'
  463. })
  464. ;
  465. $nextSide
  466. .addClass(className.animating)
  467. .css({
  468. 'display' : 'block',
  469. 'left' : box.origin + 'px',
  470. 'transform' : 'rotateY(-180deg)'
  471. })
  472. ;
  473. }
  474. },
  475. setting: function(name, value) {
  476. if(value !== undefined) {
  477. if( $.isPlainObject(name) ) {
  478. module.verbose('Modifying settings object', name, value);
  479. $.extend(true, settings, name);
  480. }
  481. else {
  482. module.verbose('Modifying setting', name, value);
  483. settings[name] = value;
  484. }
  485. }
  486. else {
  487. return settings[name];
  488. }
  489. },
  490. setting: function(name, value) {
  491. if(value !== undefined) {
  492. if( $.isPlainObject(name) ) {
  493. $.extend(true, settings, name);
  494. }
  495. else {
  496. settings[name] = value;
  497. }
  498. }
  499. else {
  500. return settings[name];
  501. }
  502. },
  503. internal: function(name, value) {
  504. if(value !== undefined) {
  505. if( $.isPlainObject(name) ) {
  506. $.extend(true, module, name);
  507. }
  508. else {
  509. module[name] = value;
  510. }
  511. }
  512. else {
  513. return module[name];
  514. }
  515. },
  516. debug: function() {
  517. if(settings.debug) {
  518. if(settings.performance) {
  519. module.performance.log(arguments);
  520. }
  521. else {
  522. module.debug = Function.prototype.bind.call(console.info, console, settings.moduleName + ':');
  523. }
  524. }
  525. },
  526. verbose: function() {
  527. if(settings.verbose && settings.debug) {
  528. if(settings.performance) {
  529. module.performance.log(arguments);
  530. }
  531. else {
  532. module.verbose = Function.prototype.bind.call(console.info, console, settings.moduleName + ':');
  533. }
  534. }
  535. },
  536. error: function() {
  537. module.error = Function.prototype.bind.call(console.error, console, settings.moduleName + ':');
  538. },
  539. performance: {
  540. log: function(message) {
  541. var
  542. currentTime,
  543. executionTime,
  544. previousTime
  545. ;
  546. if(settings.performance) {
  547. currentTime = new Date().getTime();
  548. previousTime = time || currentTime,
  549. executionTime = currentTime - previousTime;
  550. time = currentTime;
  551. performance.push({
  552. 'Element' : element,
  553. 'Name' : message[0],
  554. 'Arguments' : [].slice.call(message, 1) || '',
  555. 'Execution Time' : executionTime
  556. });
  557. }
  558. clearTimeout(module.performance.timer);
  559. module.performance.timer = setTimeout(module.performance.display, 100);
  560. },
  561. display: function() {
  562. var
  563. title = settings.moduleName + ':',
  564. totalTime = 0
  565. ;
  566. time = false;
  567. $.each(performance, function(index, data) {
  568. totalTime += data['Execution Time'];
  569. });
  570. title += ' ' + totalTime + 'ms';
  571. if(moduleSelector) {
  572. title += ' \'' + moduleSelector + '\'';
  573. }
  574. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  575. console.groupCollapsed(title);
  576. if(console.table) {
  577. console.table(performance);
  578. }
  579. else {
  580. $.each(performance, function(index, data) {
  581. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  582. });
  583. }
  584. console.groupEnd();
  585. }
  586. performance = [];
  587. }
  588. },
  589. invoke: function(query, passedArguments, context) {
  590. var
  591. searchInstance = instance,
  592. maxDepth,
  593. found
  594. ;
  595. passedArguments = passedArguments || queryArguments;
  596. context = element || context;
  597. if(typeof query == 'string' && searchInstance !== undefined) {
  598. query = query.split('.');
  599. maxDepth = query.length - 1;
  600. $.each(query, function(depth, value) {
  601. if( $.isPlainObject( searchInstance[value] ) && (depth != maxDepth) ) {
  602. searchInstance = searchInstance[value];
  603. return true;
  604. }
  605. else if( searchInstance[value] !== undefined ) {
  606. found = searchInstance[value];
  607. return true;
  608. }
  609. module.error(error.method);
  610. return false;
  611. });
  612. }
  613. if ( $.isFunction( found ) ) {
  614. instance.verbose('Executing invoked function', found);
  615. return found.apply(context, passedArguments);
  616. }
  617. return found || false;
  618. }
  619. };
  620. if(methodInvoked) {
  621. if(instance === undefined) {
  622. module.initialize();
  623. }
  624. invokedResponse = module.invoke(query);
  625. }
  626. else {
  627. if(instance !== undefined) {
  628. module.destroy();
  629. }
  630. module.initialize();
  631. }
  632. })
  633. ;
  634. return (invokedResponse)
  635. ? invokedResponse
  636. : this
  637. ;
  638. };
  639. $.fn.shape.settings = {
  640. // module info
  641. moduleName : 'Shape Module',
  642. // debug content outputted to console
  643. debug : false,
  644. // verbose debug output
  645. verbose : false,
  646. // performance data output
  647. performance: false,
  648. // event namespace
  649. namespace : 'shape',
  650. // callback occurs on side change
  651. beforeChange : function() {},
  652. onChange : function() {},
  653. // use css animation (currently only true is supported)
  654. useCSS : true,
  655. // animation duration (useful only with future js animations)
  656. duration : 1000,
  657. easing : 'easeInOutQuad',
  658. // possible errors
  659. error: {
  660. side : 'You tried to switch to a side that does not exist.',
  661. method : 'The method you called is not defined'
  662. },
  663. // classnames used
  664. className : {
  665. css : 'css',
  666. animating : 'animating',
  667. hidden : 'hidden',
  668. active : 'active'
  669. },
  670. // selectors used
  671. selector : {
  672. sides : '.sides',
  673. side : '.side'
  674. }
  675. };
  676. })( jQuery, window , document );