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

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