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.

695 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. endTransition = '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(endTransition, 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(endTransition, 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. maxDepth,
  544. found
  545. ;
  546. passedArguments = passedArguments || queryArguments;
  547. context = element || context;
  548. if(typeof query == 'string' && instance !== undefined) {
  549. query = query.split('.');
  550. maxDepth = query.length - 1;
  551. $.each(query, function(depth, value) {
  552. if( $.isPlainObject( instance[value] ) && (depth != maxDepth) ) {
  553. instance = instance[value];
  554. return true;
  555. }
  556. else if( instance[value] !== undefined ) {
  557. found = instance[value];
  558. return true;
  559. }
  560. module.error(errors.method);
  561. return false;
  562. });
  563. }
  564. if ( $.isFunction( found ) ) {
  565. module.verbose('Executing invoked function', found);
  566. return found.apply(context, passedArguments);
  567. }
  568. return found || false;
  569. }
  570. };
  571. if(methodInvoked) {
  572. if(instance === undefined) {
  573. module.initialize();
  574. }
  575. invokedResponse = module.invoke(query);
  576. }
  577. else {
  578. if(instance !== undefined) {
  579. module.destroy();
  580. }
  581. module.initialize();
  582. }
  583. })
  584. ;
  585. return (invokedResponse)
  586. ? invokedResponse
  587. : this
  588. ;
  589. };
  590. $.fn.shape.settings = {
  591. // module info
  592. moduleName : 'Shape Module',
  593. // debug content outputted to console
  594. debug : true,
  595. // verbose debug output
  596. verbose : true,
  597. // performance data output
  598. performance: true,
  599. // event namespace
  600. namespace : 'shape',
  601. // callback occurs on side change
  602. beforeChange : function() {},
  603. onChange : function() {},
  604. // use css animation (currently only true is supported)
  605. useCSS : true,
  606. // animation duration (useful only with future js animations)
  607. duration : 1000,
  608. easing : 'easeInOutQuad',
  609. // possible errors
  610. error: {
  611. side : 'You tried to switch to a side that does not exist.',
  612. method : 'The method you called is not defined'
  613. },
  614. // classnames used
  615. className : {
  616. css : 'css',
  617. animating : 'animating',
  618. hidden : 'hidden',
  619. active : 'active'
  620. },
  621. // selectors used
  622. selector : {
  623. sides : '.sides',
  624. side : '.side'
  625. }
  626. };
  627. })( jQuery, window , document );