PageRenderTime 56ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/app/assets/javascripts/store/head-turn.js

https://github.com/brianmcguinty/rowley
JavaScript | 263 lines | 149 code | 50 blank | 64 comment | 32 complexity | bf89cf13f0249b170a1bd6d85913924e MD5 | raw file
Possible License(s): BSD-3-Clause
  1. // Info on the user's browser
  2. var userBrowser = {
  3. 'name': '',
  4. 'version': 0
  5. };
  6. jQuery(document).ready(function() {
  7. // IE detection code from http://www.javascriptkit.com/javatutors/navigator.shtml
  8. if (/MSIE (\d+\.\d+);/.test(navigator.userAgent)){
  9. userBrowser = {
  10. 'name': 'ie',
  11. 'version': new Number(RegExp.$1)
  12. };
  13. }
  14. });
  15. (function ($) {
  16. /*
  17. An object containing info on each of the headturns on the page.
  18. Look up a headturn by its id to get info about it.
  19. Sample key-value pair:
  20. 'someId': {
  21. 'picCount': 7,
  22. 'currentIndex': 0,
  23. 'defaultIndex': 3,
  24. 'mouseHovered': false,
  25. 'compoundImage': a jQuery object of the big headturn image
  26. }
  27. */
  28. var allHeadTurns = {};
  29. // Given an image and its container (both jQuery objects), returns the number of times that the image
  30. // will fill the container's supplied dimension, either width or height.
  31. // Returns a whole number 1 or higher, or NaN if the calculation has invalid inputs.
  32. function getPicCount(imageDimension, containerDimension) {
  33. return Math.max( 1, Math.ceil( imageDimension / containerDimension ) );
  34. }
  35. // Returns the structured data on a frame given the frame's jQuery object
  36. function getFrame(frame) {
  37. return allHeadTurns[frame.data('identifier')];
  38. }
  39. function headturnInteraction(evt) {
  40. evt.preventDefault(); // Suppress default touchmove behavior in touch UIs
  41. var pageX;
  42. // Detect whether it's a touch interaction or not, and fetch pageX accordingly.
  43. if (evt.originalEvent.touches) {
  44. pageX = evt.originalEvent.touches[evt.originalEvent.touches.length-1].pageX;
  45. }
  46. else {
  47. pageX = evt.pageX;
  48. }
  49. // Remove any helper overlays (snap out in IE, b/c its fadeOut looks bad on alpha pngs)
  50. var helpOverlay = jQuery(this).find('.help-overlay');
  51. if (userBrowser['name'] == 'ie' && userBrowser['version'] < 9) {
  52. helpOverlay.hide();
  53. }
  54. else {
  55. helpOverlay.fadeOut('fast');
  56. }
  57. var frame = getFrame(jQuery(this));
  58. var mouseJustEntered = (frame['mouseHovered'] == false) ? true : false;
  59. frame['mouseHovered'] = true;
  60. // How far across the image horizontally is the cursor? 0 <= percentAcross <= 1
  61. var percentAcross = (pageX - jQuery(this).offset().left) / jQuery(this).width();
  62. var targetIndex;
  63. // Based on percentAcross and the picCount, calculate which image to display.
  64. for (var i=0; i < frame['picCount']; i++) {
  65. if (i/frame['picCount'] < percentAcross && (i+1)/frame['picCount'] > percentAcross) {
  66. targetIndex = i;
  67. break;
  68. }
  69. }
  70. // Animate to the target image
  71. if (!isNaN(targetIndex) && targetIndex != frame['currentIndex']) {
  72. animateModel(frame, frame['currentIndex'], targetIndex, mouseJustEntered);
  73. }
  74. }
  75. // Moves the model smoothly through the intermediate images rather than just
  76. // just snapping directly to the target image.
  77. function animateModel(frame, startIndex, targetIndex, delay, dontThrottle) {
  78. // Time in ms. A mousemove sooner than this after the previous mousemove won't fire a new headturn recursion.
  79. var MIN_BTWN_MOVES = 50;
  80. // Animation delay, in ms
  81. var STD_FRAME_DELAY = 20;
  82. // Commented out b/c a frame delay seems to always look better. But I'm leaving
  83. // the code here in case we want to start selectively turning off the delay.
  84. // var frameDelay = (delay) ? STD_FRAME_DELAY : 0;
  85. var frameDelay = STD_FRAME_DELAY;
  86. // Make CSS adjustments for clients that need it (e.g. Mobile Safari).
  87. // It's an integer representing pixels. Negative bumps left, positive bumps right.
  88. // The 0.0034 multiplier is the arbitrary amount that fixes Mobile Safari's imprecision.
  89. var adjustment = (isMobileSafari) ? Math.round(frame['picWidth']* 0.0034) : 0;
  90. var now = new Date();
  91. // This condition prevents from headturns being fired in too-rapid succession.
  92. if (!lastHeadturnMove || now - lastHeadturnMove > MIN_BTWN_MOVES || dontThrottle) {
  93. lastHeadturnMove = now;
  94. var i;
  95. for (i = 0; i < frame['picCount']; i++) {
  96. if (i == startIndex) {
  97. var newLeft = 0 - (i * frame['picWidth']) + (adjustment * i);
  98. frame['compoundImage'].attr('style', 'left: '+ newLeft +'px !important');
  99. }
  100. }
  101. frame['currentIndex'] = startIndex;
  102. setTimeout(
  103. function() {
  104. if (startIndex != targetIndex) {
  105. var step = (startIndex > targetIndex) ? -1 : 1;
  106. animateModel(frame, startIndex+step, targetIndex, frameDelay, true);
  107. }
  108. },
  109. frameDelay
  110. );
  111. }
  112. }
  113. // Return model to default
  114. function turnToDefault(evt) {
  115. var frame = getFrame(jQuery(this));
  116. frame['mouseHovered'] = false;
  117. // There's a short delay to prevent flickering when the the mouse
  118. // leaves the wrapper and then immediately reenters. It doesn't *have*
  119. // to be 250ms, but that's about the right amount of time.
  120. setTimeout(
  121. function() {
  122. if (frame['mouseHovered'] != true) {
  123. animateModel(frame, frame['currentIndex'], frame['defaultIndex'], true);
  124. }
  125. },
  126. 250
  127. );
  128. }
  129. // Variables for timing interactions with the headturn
  130. var lastHeadturnMove = null;
  131. // Boolean, true if the user is on an iPhone, iPod, or iPad
  132. var isMobileSafari = false;
  133. // Run when the page is ready
  134. jQuery(document).ready(function() {
  135. var headturnImageWraps = jQuery('.headturn-image-wrap');
  136. // Detect whether this is a Mobile Safari device. From code at:
  137. // http://www.sitepoint.com/identify-apple-iphone-ipod-ipad-visitors
  138. var appleDeviceInfo = {};
  139. appleDeviceInfo.ua = navigator.userAgent;
  140. appleDeviceInfo.types = ['iPhone', 'iPod', 'iPad'];
  141. for (var d = 0; d < appleDeviceInfo.types.length; d++) {
  142. var t = appleDeviceInfo.types[d];
  143. appleDeviceInfo[t] = !!appleDeviceInfo.ua.match(new RegExp(t, 'i'));
  144. isMobileSafari = isMobileSafari || appleDeviceInfo[t];
  145. }
  146. jQuery(window).load(function() {
  147. // Prep work for each headturn. This gets done once to avoid having to do
  148. // it after every interaction event with the headturn. Wrapped in load()
  149. // because you can't measure an image's width until it has loaded.
  150. headturnImageWraps.each(function(i) {
  151. var headturnWrap = jQuery(this);
  152. var headturnImage = headturnWrap.find('.headturn-image').eq(0);
  153. var htWrapStyleMap = {'overflow': 'hidden'};
  154. if ( headturnWrap.css('position') == 'static' ) {
  155. htWrapStyleMap['position'] = 'relative';
  156. }
  157. headturnWrap.css(htWrapStyleMap);
  158. if ( headturnImage.css('position') == 'static' ) {
  159. headturnImage.css('position', 'absolute');
  160. }
  161. var thisHeadTurn = {};
  162. // Figure out pic width in pixels (assumes all the pics in the headturn have the same width)
  163. thisHeadTurn['picWidth'] = headturnWrap.width();
  164. // Automatically figure out the number of pics in the headturn. Have to make
  165. // sure it's visible, because jQuery returns a width of 0 for hidden images.
  166. var startedHidden = headturnWrap.is(':visible') === false;
  167. if (startedHidden) {
  168. // If the headturn image isn't visible, we make an out-of-the-viewport clone, measure
  169. // its dimensions, and then get rid of it. We could just try to show the image
  170. // itself, briefly and out of the viewport, but that won't work if it's nested
  171. // within a parent and the parent is hidden.
  172. var tempMeasHeadturnImage = headturnImage.clone(false);
  173. tempMeasHeadturnImage.css({
  174. display: 'block',
  175. position: 'absolute',
  176. top: '-20000px',
  177. left: '-20000px'
  178. });
  179. jQuery('body').append(tempMeasHeadturnImage);
  180. thisHeadTurn['picCount'] = getPicCount(tempMeasHeadturnImage.width(), thisHeadTurn['picWidth']);
  181. tempMeasHeadturnImage.remove();
  182. }
  183. else {
  184. thisHeadTurn['picCount'] = getPicCount(headturnImage.width(), thisHeadTurn['picWidth']);
  185. }
  186. // Figure out default index (0-indexed) and set current index
  187. // Default index is the middle one, unless the headturn wrap
  188. // has a "no-center" class, then it's 0.
  189. thisHeadTurn['defaultIndex'] = (headturnWrap.hasClass('no-center')) ? 0 : Math.floor(thisHeadTurn['picCount'] / 2);
  190. thisHeadTurn['currentIndex'] = thisHeadTurn['defaultIndex'];
  191. // mouseHovered keeps track of whether the mouse is currently on the headturn (obviously).
  192. // It's useful for events that are delayed responses to mouseleave.
  193. thisHeadTurn['mouseHovered'] = false;
  194. // Save the jQuery object of the compound image, for later use
  195. thisHeadTurn['compoundImage'] = headturnImage;
  196. headturnWrap.data('identifier', headturnImageWraps.index(headturnWrap));
  197. allHeadTurns[headturnWrap.data('identifier')] = thisHeadTurn;
  198. // Prime it
  199. headturnImage.css({
  200. position: 'relative',
  201. left: 0 - (thisHeadTurn['picWidth'] * thisHeadTurn['defaultIndex'])
  202. });
  203. animateModel(thisHeadTurn, thisHeadTurn['currentIndex'], thisHeadTurn['defaultIndex'], false);
  204. });
  205. });
  206. // Listening for interaction with headturns. Touchmove covers drag events from touch UIs.
  207. headturnImageWraps.mousemove(headturnInteraction);
  208. headturnImageWraps.live('touchmove', headturnInteraction);
  209. // On mouseleave (or touchend, for touch UIs), returns the model's head to the default positon.
  210. //headturnImageWraps.mouseleave(turnToDefault);
  211. //headturnImageWraps.live('touchend', turnToDefault);
  212. });
  213. })(jQuery);