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.

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