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.

697 lines
21 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. transitionEnd = 'msTransitionEnd oTransitionEnd webkitTransitionEnd',
  34. // standard module
  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.verbose('Executing animation callback', event);
  76. event.stopPropagation();
  77. module.reset();
  78. module.set.active();
  79. $.proxy(settings.onChange, $nextSide)();
  80. };
  81. if(settings.useCSS) {
  82. module.verbose('Starting CSS animation');
  83. $module
  84. .addClass(className.animating)
  85. ;
  86. module.set.stageSize();
  87. module.repaint();
  88. $module
  89. .addClass(className.css)
  90. ;
  91. $activeSide
  92. .addClass(className.hidden)
  93. ;
  94. $sides
  95. .css(propertyObject)
  96. .one(transitionEnd + eventNamespace, callback)
  97. ;
  98. }
  99. else {
  100. // not yet supported until .animate() is extended to allow RotateX/Y
  101. module.verbose('Starting javascript animation');
  102. $module
  103. .addClass(className.animating)
  104. .removeClass(className.css)
  105. ;
  106. module.set.stageSize();
  107. module.repaint();
  108. $activeSide
  109. .animate({
  110. opacity: 0
  111. }, settings.duration, settings.easing)
  112. ;
  113. $sides
  114. .animate(propertyObject, settings.duration, settings.easing, callback)
  115. ;
  116. }
  117. },
  118. queue: function(method) {
  119. module.debug('Queueing animation of', method);
  120. $sides
  121. .one(transitionEnd, function() {
  122. module.debug('Executing queued animation');
  123. $module.shape(method);
  124. })
  125. ;
  126. },
  127. reset: function() {
  128. module.verbose('Animating states reset');
  129. $module
  130. .removeClass(className.css)
  131. .removeClass(className.animating)
  132. .removeAttr('style')
  133. ;
  134. $sides
  135. .removeAttr('style')
  136. ;
  137. $side
  138. .removeAttr('style')
  139. .removeClass(className.hidden)
  140. ;
  141. $nextSide
  142. .removeClass(className.animating)
  143. .removeAttr('style')
  144. ;
  145. },
  146. is: {
  147. animating: function() {
  148. return $module.hasClass(className.animating);
  149. }
  150. },
  151. get: {
  152. nextSide: function() {
  153. return ( $activeSide.next(settings.selector.side).size() > 0 )
  154. ? $activeSide.next(settings.selector.side)
  155. : $module.find(settings.selector.side).first()
  156. ;
  157. }
  158. },
  159. set: {
  160. defaultSide: function() {
  161. $activeSide = $module.find('.' + settings.className.active);
  162. $nextSide = ( $activeSide.next(settings.selector.side).size() > 0 )
  163. ? $activeSide.next(settings.selector.side)
  164. : $module.find(settings.selector.side).first()
  165. ;
  166. module.verbose('Active side set to', $activeSide);
  167. module.verbose('Next side set to', $nextSide);
  168. },
  169. stageSize: function() {
  170. var
  171. stage = {
  172. width : $nextSide.outerWidth(),
  173. height : $nextSide.outerHeight()
  174. }
  175. ;
  176. module.verbose('Resizing stage to fit new content', stage);
  177. $module
  178. .css({
  179. width : stage.width,
  180. height : stage.height
  181. })
  182. ;
  183. },
  184. nextSide: function(selector) {
  185. $nextSide = $module.find(selector);
  186. if($nextSide.size() === 0) {
  187. module.error(error.side);
  188. }
  189. module.verbose('Next side manually set to', $nextSide);
  190. },
  191. active: function() {
  192. module.verbose('Setting new side to active', $nextSide);
  193. $side
  194. .removeClass(className.active)
  195. ;
  196. $nextSide
  197. .addClass(className.active)
  198. ;
  199. module.set.defaultSide();
  200. }
  201. },
  202. flip: {
  203. up: function() {
  204. module.debug('Flipping up', $nextSide);
  205. if( !module.is.animating() ) {
  206. module.stage.above();
  207. module.animate( module.getTransform.up() );
  208. }
  209. else {
  210. module.queue('flip.up');
  211. }
  212. },
  213. down: function() {
  214. module.debug('Flipping down', $nextSide);
  215. if( !module.is.animating() ) {
  216. module.stage.below();
  217. module.animate( module.getTransform.down() );
  218. }
  219. else {
  220. module.queue('flip.down');
  221. }
  222. },
  223. left: function() {
  224. module.debug('Flipping left', $nextSide);
  225. if( !module.is.animating() ) {
  226. module.stage.left();
  227. module.animate(module.getTransform.left() );
  228. }
  229. else {
  230. module.queue('flip.left');
  231. }
  232. },
  233. right: function() {
  234. module.debug('Flipping right', $nextSide);
  235. if( !module.is.animating() ) {
  236. module.stage.right();
  237. module.animate(module.getTransform.right() );
  238. }
  239. else {
  240. module.queue('flip.right');
  241. }
  242. },
  243. over: function() {
  244. module.debug('Flipping over', $nextSide);
  245. if( !module.is.animating() ) {
  246. module.stage.behind();
  247. module.animate(module.getTransform.behind() );
  248. }
  249. else {
  250. module.queue('flip.over');
  251. }
  252. }
  253. },
  254. getTransform: {
  255. up: function() {
  256. var
  257. translate = {
  258. y: -(($activeSide.outerHeight() - $nextSide.outerHeight()) / 2),
  259. z: -($activeSide.outerHeight() / 2)
  260. }
  261. ;
  262. return {
  263. transform: 'translateY(' + translate.y + 'px) translateZ('+ translate.z + 'px) rotateX(-90deg)'
  264. };
  265. },
  266. down: function() {
  267. var
  268. translate = {
  269. y: -(($activeSide.outerHeight() - $nextSide.outerHeight()) / 2),
  270. z: -($activeSide.outerHeight() / 2)
  271. }
  272. ;
  273. return {
  274. transform: 'translateY(' + translate.y + 'px) translateZ('+ translate.z + 'px) rotateX(90deg)'
  275. };
  276. },
  277. left: function() {
  278. var
  279. translate = {
  280. x : -(($activeSide.outerWidth() - $nextSide.outerWidth()) / 2),
  281. z : -($activeSide.outerWidth() / 2)
  282. }
  283. ;
  284. return {
  285. transform: 'translateX(' + translate.x + 'px) translateZ(' + translate.z + 'px) rotateY(90deg)'
  286. };
  287. },
  288. right: function() {
  289. var
  290. translate = {
  291. x : -(($activeSide.outerWidth() - $nextSide.outerWidth()) / 2),
  292. z : -($activeSide.outerWidth() / 2)
  293. }
  294. ;
  295. return {
  296. transform: 'translateX(' + translate.x + 'px) translateZ(' + translate.z + 'px) rotateY(-90deg)'
  297. };
  298. },
  299. behind: function() {
  300. var
  301. translate = {
  302. x : -(($activeSide.outerWidth() - $nextSide.outerWidth()) / 2)
  303. }
  304. ;
  305. return {
  306. transform: 'translateX(' + translate.x + 'px) rotateY(180deg)'
  307. };
  308. }
  309. },
  310. stage: {
  311. above: function() {
  312. var
  313. box = {
  314. origin : (($activeSide.outerHeight() - $nextSide.outerHeight()) / 2),
  315. depth : {
  316. active : ($nextSide.outerHeight() / 2),
  317. next : ($activeSide.outerHeight() / 2)
  318. }
  319. }
  320. ;
  321. module.verbose('Setting the initial animation position as above', $nextSide, box);
  322. $activeSide
  323. .css({
  324. 'transform' : 'rotateY(0deg) translateZ(' + box.depth.active + 'px)'
  325. })
  326. ;
  327. $nextSide
  328. .addClass(className.animating)
  329. .css({
  330. 'display' : 'block',
  331. 'top' : box.origin + 'px',
  332. 'transform' : 'rotateX(90deg) translateZ(' + box.depth.next + 'px)'
  333. })
  334. ;
  335. },
  336. below: function() {
  337. var
  338. box = {
  339. origin : (($activeSide.outerHeight() - $nextSide.outerHeight()) / 2),
  340. depth : {
  341. active : ($nextSide.outerHeight() / 2),
  342. next : ($activeSide.outerHeight() / 2)
  343. }
  344. }
  345. ;
  346. module.verbose('Setting the initial animation position as below', $nextSide, box);
  347. $activeSide
  348. .css({
  349. 'transform' : 'rotateY(0deg) translateZ(' + box.depth.active + 'px)'
  350. })
  351. ;
  352. $nextSide
  353. .addClass(className.animating)
  354. .css({
  355. 'display' : 'block',
  356. 'top' : box.origin + 'px',
  357. 'transform' : 'rotateX(-90deg) translateZ(' + box.depth.next + 'px)'
  358. })
  359. ;
  360. },
  361. left: function() {
  362. var
  363. box = {
  364. origin : ( ( $activeSide.outerWidth() - $nextSide.outerWidth() ) / 2),
  365. depth : {
  366. active : ($nextSide.outerWidth() / 2),
  367. next : ($activeSide.outerWidth() / 2)
  368. }
  369. }
  370. ;
  371. module.verbose('Setting the initial animation position as left', $nextSide, box);
  372. $activeSide
  373. .css({
  374. 'transform' : 'rotateY(0deg) translateZ(' + box.depth.active + 'px)'
  375. })
  376. ;
  377. $nextSide
  378. .addClass(className.animating)
  379. .css({
  380. 'display' : 'block',
  381. 'left' : box.origin + 'px',
  382. 'transform' : 'rotateY(-90deg) translateZ(' + box.depth.next + 'px)'
  383. })
  384. ;
  385. },
  386. right: function() {
  387. var
  388. box = {
  389. origin : ( ( $activeSide.outerWidth() - $nextSide.outerWidth() ) / 2),
  390. depth : {
  391. active : ($nextSide.outerWidth() / 2),
  392. next : ($activeSide.outerWidth() / 2)
  393. }
  394. }
  395. ;
  396. module.verbose('Setting the initial animation position as left', $nextSide, box);
  397. $activeSide
  398. .css({
  399. 'transform' : 'rotateY(0deg) translateZ(' + box.depth.active + 'px)'
  400. })
  401. ;
  402. $nextSide
  403. .addClass(className.animating)
  404. .css({
  405. 'display' : 'block',
  406. 'left' : box.origin + 'px',
  407. 'transform' : 'rotateY(90deg) translateZ(' + box.depth.next + 'px)'
  408. })
  409. ;
  410. },
  411. behind: function() {
  412. var
  413. box = {
  414. origin : ( ( $activeSide.outerWidth() - $nextSide.outerWidth() ) / 2),
  415. depth : {
  416. active : ($nextSide.outerWidth() / 2),
  417. next : ($activeSide.outerWidth() / 2)
  418. }
  419. }
  420. ;
  421. module.verbose('Setting the initial animation position as behind', $nextSide, box);
  422. $activeSide
  423. .css({
  424. 'transform' : 'rotateY(0deg)'
  425. })
  426. ;
  427. $nextSide
  428. .addClass(className.animating)
  429. .css({
  430. 'display' : 'block',
  431. 'left' : box.origin + 'px',
  432. 'transform' : 'rotateY(-180deg)'
  433. })
  434. ;
  435. }
  436. },
  437. setting: function(name, value) {
  438. if(value !== undefined) {
  439. if( $.isPlainObject(name) ) {
  440. module.verbose('Modifying settings object', name, value);
  441. $.extend(true, settings, name);
  442. }
  443. else {
  444. module.verbose('Modifying setting', name, value);
  445. settings[name] = value;
  446. }
  447. }
  448. else {
  449. return settings[name];
  450. }
  451. },
  452. internal: function(name, value) {
  453. if(value !== undefined) {
  454. if( $.isPlainObject(name) ) {
  455. module.verbose('Modifying internal property', name, value);
  456. $.extend(true, module, name);
  457. }
  458. else {
  459. module.verbose('Changing internal method to', value);
  460. module[name] = value;
  461. }
  462. }
  463. else {
  464. return module[name];
  465. }
  466. },
  467. debug: function() {
  468. if(settings.debug) {
  469. if(settings.performance) {
  470. module.performance.log(arguments);
  471. }
  472. else {
  473. module.debug = Function.prototype.bind.call(console.info, console, settings.moduleName + ':');
  474. }
  475. }
  476. },
  477. verbose: function() {
  478. if(settings.verbose && settings.debug) {
  479. if(settings.performance) {
  480. module.performance.log(arguments);
  481. }
  482. else {
  483. module.verbose = Function.prototype.bind.call(console.info, console, settings.moduleName + ':');
  484. }
  485. }
  486. },
  487. error: function() {
  488. module.error = Function.prototype.bind.call(console.log, console, settings.moduleName + ':');
  489. },
  490. performance: {
  491. log: function(message) {
  492. var
  493. currentTime,
  494. executionTime,
  495. previousTime
  496. ;
  497. if(settings.performance) {
  498. currentTime = new Date().getTime();
  499. previousTime = time || currentTime,
  500. executionTime = currentTime - previousTime;
  501. time = currentTime;
  502. performance.push({
  503. 'Element' : element,
  504. 'Name' : message[0],
  505. 'Arguments' : message[1] || 'None',
  506. 'Execution Time' : executionTime
  507. });
  508. clearTimeout(module.performance.timer);
  509. module.performance.timer = setTimeout(module.performance.display, 100);
  510. }
  511. },
  512. display: function() {
  513. var
  514. title = settings.moduleName,
  515. caption = settings.moduleName + ': ' + moduleSelector + '(' + $allModules.size() + ' elements)',
  516. totalExecutionTime = 0
  517. ;
  518. if(moduleSelector) {
  519. title += ' Performance (' + moduleSelector + ')';
  520. }
  521. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  522. console.groupCollapsed(title);
  523. if(console.table) {
  524. $.each(performance, function(index, data) {
  525. totalExecutionTime += data['Execution Time'];
  526. });
  527. console.table(performance);
  528. }
  529. else {
  530. $.each(performance, function(index, data) {
  531. totalExecutionTime += data['Execution Time'];
  532. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  533. });
  534. }
  535. console.log('Total Execution Time:', totalExecutionTime +'ms');
  536. console.groupEnd();
  537. performance = [];
  538. time = false;
  539. }
  540. }
  541. },
  542. invoke: function(query, passedArguments, context) {
  543. var
  544. searchInstance = instance,
  545. maxDepth,
  546. found
  547. ;
  548. passedArguments = passedArguments || queryArguments;
  549. context = element || context;
  550. if(typeof query == 'string' && searchInstance !== undefined) {
  551. query = query.split('.');
  552. maxDepth = query.length - 1;
  553. $.each(query, function(depth, value) {
  554. if( $.isPlainObject( searchInstance[value] ) && (depth != maxDepth) ) {
  555. searchInstance = searchInstance[value];
  556. return true;
  557. }
  558. else if( searchInstance[value] !== undefined ) {
  559. found = searchInstance[value];
  560. return true;
  561. }
  562. module.error(error.method);
  563. return false;
  564. });
  565. }
  566. if ( $.isFunction( found ) ) {
  567. instance.verbose('Executing invoked function', found);
  568. return found.apply(context, passedArguments);
  569. }
  570. return found || false;
  571. }
  572. };
  573. if(methodInvoked) {
  574. if(instance === undefined) {
  575. module.initialize();
  576. }
  577. invokedResponse = module.invoke(query);
  578. }
  579. else {
  580. if(instance !== undefined) {
  581. module.destroy();
  582. }
  583. module.initialize();
  584. }
  585. })
  586. ;
  587. return (invokedResponse)
  588. ? invokedResponse
  589. : this
  590. ;
  591. };
  592. $.fn.shape.settings = {
  593. // module info
  594. moduleName : 'Shape Module',
  595. // debug content outputted to console
  596. debug : false,
  597. // verbose debug output
  598. verbose : false,
  599. // performance data output
  600. performance: false,
  601. // event namespace
  602. namespace : 'shape',
  603. // callback occurs on side change
  604. beforeChange : function() {},
  605. onChange : function() {},
  606. // use css animation (currently only true is supported)
  607. useCSS : true,
  608. // animation duration (useful only with future js animations)
  609. duration : 1000,
  610. easing : 'easeInOutQuad',
  611. // possible errors
  612. error: {
  613. side : 'You tried to switch to a side that does not exist.',
  614. method : 'The method you called is not defined'
  615. },
  616. // classnames used
  617. className : {
  618. css : 'css',
  619. animating : 'animating',
  620. hidden : 'hidden',
  621. active : 'active'
  622. },
  623. // selectors used
  624. selector : {
  625. sides : '.sides',
  626. side : '.side'
  627. }
  628. };
  629. })( jQuery, window , document );