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

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. 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 );