skrollr.menu.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. /*!
  2. * Plugin for skrollr.
  3. * This plugin makes hashlinks scroll nicely to their target position.
  4. *
  5. * Alexander Prinzhorn - https://github.com/Prinzhorn/skrollr
  6. *
  7. * Free to use under terms of MIT license
  8. */
  9. (function(document, window) {
  10. 'use strict';
  11. var DEFAULT_DURATION = 500;
  12. var DEFAULT_EASING = 'sqrt';
  13. var DEFAULT_SCALE = 1;
  14. var MENU_TOP_ATTR = 'data-menu-top';
  15. var MENU_OFFSET_ATTR = 'data-menu-offset';
  16. var MENU_DURATION_ATTR = 'data-menu-duration';
  17. var MENU_IGNORE_ATTR = 'data-menu-ignore';
  18. var skrollr = window.skrollr;
  19. var history = window.history;
  20. var supportsHistory = !!history.pushState;
  21. /*
  22. Since we are using event bubbling, the element that has been clicked
  23. might not acutally be the link but a child.
  24. */
  25. var findParentLink = function(element) {
  26. //We reached the top, no link found.
  27. if(element === document) {
  28. return false;
  29. }
  30. //Yay, it's a link!
  31. if(element.tagName.toUpperCase() === 'A') {
  32. return element;
  33. }
  34. //Maybe the parent is a link.
  35. return findParentLink(element.parentNode);
  36. };
  37. /*
  38. Handle the click event on the document.
  39. */
  40. var handleClick = function(e) {
  41. //Only handle left click.
  42. if(e.which !== 1 && e.button !== 0) {
  43. return;
  44. }
  45. var link = findParentLink(e.target);
  46. //The click did not happen inside a link.
  47. if(!link) {
  48. return;
  49. }
  50. if(handleLink(link)) {
  51. e.preventDefault();
  52. }
  53. };
  54. /*
  55. Handles the click on a link. May be called without an actual click event.
  56. When the fake flag is set, the link won't change the url and the position won't be animated.
  57. */
  58. var handleLink = function(link, fake) {
  59. var hash;
  60. //When complexLinks is enabled, we also accept links which do not just contain a simple hash.
  61. if(_complexLinks) {
  62. //The link points to something completely different.
  63. if(link.hostname !== window.location.hostname) {
  64. return false;
  65. }
  66. //The link does not link to the same page/path.
  67. if(link.pathname !== document.location.pathname) {
  68. return false;
  69. }
  70. hash = link.hash;
  71. } else {
  72. //Don't use the href property (link.href) because it contains the absolute url.
  73. hash = link.getAttribute('href');
  74. }
  75. //Not a hash link.
  76. if(!/^#/.test(hash)) {
  77. return false;
  78. }
  79. //The link has the ignore attribute.
  80. if(!fake && link.getAttribute(MENU_IGNORE_ATTR) !== null) {
  81. return false;
  82. }
  83. //Now get the targetTop to scroll to.
  84. var targetTop;
  85. var menuTop;
  86. //If there's a handleLink function, it overrides the actual anchor offset.
  87. if(_handleLink) {
  88. menuTop = _handleLink(link);
  89. }
  90. //If there's a data-menu-top attribute and no handleLink function, it overrides the actual anchor offset.
  91. else {
  92. menuTop = link.getAttribute(MENU_TOP_ATTR);
  93. }
  94. if(menuTop !== null) {
  95. //Is it a percentage offset?
  96. if(/p$/.test(menuTop)) {
  97. targetTop = (menuTop.slice(0, -1) / 100) * document.documentElement.clientHeight;
  98. } else {
  99. targetTop = +menuTop * _scale;
  100. }
  101. } else {
  102. var scrollTarget = document.getElementById(hash.substr(1));
  103. //Ignore the click if no target is found.
  104. if(!scrollTarget) {
  105. return false;
  106. }
  107. targetTop = _skrollrInstance.relativeToAbsolute(scrollTarget, 'top', 'top');
  108. var menuOffset = scrollTarget.getAttribute(MENU_OFFSET_ATTR);
  109. if(menuOffset !== null) {
  110. targetTop += +menuOffset;
  111. }
  112. }
  113. if(supportsHistory && _updateUrl && !fake) {
  114. history.pushState({top: targetTop}, '', hash);
  115. }
  116. var menuDuration = parseInt(link.getAttribute(MENU_DURATION_ATTR), 10);
  117. var animationDuration = _duration(_skrollrInstance.getScrollTop(), targetTop);
  118. if(!isNaN(menuDuration)) {
  119. animationDuration = menuDuration;
  120. }
  121. //Trigger the change if event if there's a listener.
  122. if(_change) {
  123. _change(hash, targetTop);
  124. }
  125. //Now finally scroll there.
  126. if(_animate && !fake) {
  127. _skrollrInstance.animateTo(targetTop, {
  128. duration: animationDuration,
  129. easing: _easing
  130. });
  131. } else {
  132. defer(function() {
  133. _skrollrInstance.setScrollTop(targetTop);
  134. });
  135. }
  136. return true;
  137. };
  138. var jumpStraightToHash = function() {
  139. if(window.location.hash && document.querySelector) {
  140. var link = document.querySelector('a[href="' + window.location.hash + '"]');
  141. if(!link) {
  142. // No link found on page, so we create one and then activate it
  143. link = document.createElement('a');
  144. link.href = window.location.hash;
  145. }
  146. handleLink(link, true);
  147. }
  148. };
  149. var defer = function(fn) {
  150. window.setTimeout(fn, 1);
  151. };
  152. /*
  153. Global menu function accessible through window.skrollr.menu.init.
  154. */
  155. skrollr.menu = {};
  156. skrollr.menu.init = function(skrollrInstance, options) {
  157. _skrollrInstance = skrollrInstance;
  158. options = options || {};
  159. _easing = options.easing || DEFAULT_EASING;
  160. _animate = options.animate !== false;
  161. _duration = options.duration || DEFAULT_DURATION;
  162. _handleLink = options.handleLink;
  163. _scale = options.scale || DEFAULT_SCALE;
  164. _complexLinks = options.complexLinks === true;
  165. _change = options.change;
  166. _updateUrl = options.updateUrl !== false;
  167. if(typeof _duration === 'number') {
  168. _duration = (function(duration) {
  169. return function() {
  170. return duration;
  171. };
  172. }(_duration));
  173. }
  174. //Use event bubbling and attach a single listener to the document.
  175. skrollr.addEvent(document, 'click', handleClick);
  176. if(supportsHistory) {
  177. skrollr.addEvent(window, 'popstate', function(e) {
  178. var state = e.state || {};
  179. var top = state.top || 0;
  180. defer(function() {
  181. _skrollrInstance.setScrollTop(top);
  182. });
  183. }, false);
  184. }
  185. jumpStraightToHash();
  186. };
  187. //Expose the handleLink function to be able to programmatically trigger clicks.
  188. skrollr.menu.click = function(link) {
  189. //We're not assigning it directly to `click` because of the second ("private") parameter.
  190. handleLink(link);
  191. };
  192. //Private reference to the initialized skrollr.
  193. var _skrollrInstance;
  194. var _easing;
  195. var _duration;
  196. var _animate;
  197. var _handleLink;
  198. var _scale;
  199. var _complexLinks;
  200. var _change;
  201. var _updateUrl;
  202. //In case the page was opened with a hash, prevent jumping to it.
  203. //http://stackoverflow.com/questions/3659072/jquery-disable-anchor-jump-when-loading-a-page
  204. defer(function() {
  205. if(window.location.hash) {
  206. window.scrollTo(0, 0);
  207. }
  208. });
  209. }(document, window));