SmoothScroll.min.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. // SmoothScroll for websites v1.2.1
  2. // Licensed under the terms of the MIT license.
  3. // People involved
  4. // - Balazs Galambosi (maintainer)
  5. // - Michael Herf (Pulse Algorithm)
  6. (function(){
  7. // Scroll Variables (tweakable)
  8. var defaultOptions = {
  9. // Scrolling Core
  10. frameRate : 150, // [Hz]
  11. animationTime : 1800, // [px]
  12. stepSize : 85, // [px]
  13. // Pulse (less tweakable)
  14. // ratio of "tail" to "acceleration"
  15. pulseAlgorithm : true,
  16. pulseScale : 8,
  17. pulseNormalize : 1,
  18. // Acceleration
  19. accelerationDelta : 20, // 20
  20. accelerationMax : 1, // 1
  21. // Keyboard Settings
  22. keyboardSupport : true, // option
  23. arrowScroll : 50, // [px]
  24. // Other
  25. touchpadSupport : true,
  26. fixedBackground : true,
  27. excluded : ""
  28. };
  29. var options = defaultOptions;
  30. // Other Variables
  31. var isExcluded = false;
  32. var isFrame = false;
  33. var direction = { x: 0, y: 0 };
  34. var initDone = false;
  35. var root = document.documentElement;
  36. var activeElement;
  37. var observer;
  38. var deltaBuffer = [ 120, 120, 120 ];
  39. var key = { left: 37, up: 38, right: 39, down: 40, spacebar: 32,
  40. pageup: 33, pagedown: 34, end: 35, home: 36 };
  41. /***********************************************
  42. * SETTINGS
  43. ***********************************************/
  44. var options = defaultOptions;
  45. /***********************************************
  46. * INITIALIZE
  47. ***********************************************/
  48. /**
  49. * Tests if smooth scrolling is allowed. Shuts down everything if not.
  50. */
  51. function initTest() {
  52. var disableKeyboard = false;
  53. // disable keyboard support if anything above requested it
  54. if (disableKeyboard) {
  55. removeEvent("keydown", keydown);
  56. }
  57. if (options.keyboardSupport && !disableKeyboard) {
  58. addEvent("keydown", keydown);
  59. }
  60. }
  61. /**
  62. * Sets up scrolls array, determines if frames are involved.
  63. */
  64. function init() {
  65. if (!document.body) return;
  66. var body = document.body;
  67. var html = document.documentElement;
  68. var windowHeight = window.innerHeight;
  69. var scrollHeight = body.scrollHeight;
  70. // check compat mode for root element
  71. root = (document.compatMode.indexOf('CSS') >= 0) ? html : body;
  72. activeElement = body;
  73. initTest();
  74. initDone = true;
  75. // Checks if this script is running in a frame
  76. if (top != self) {
  77. isFrame = true;
  78. }
  79. /**
  80. * This fixes a bug where the areas left and right to
  81. * the content does not trigger the onmousewheel event
  82. * on some pages. e.g.: html, body { height: 100% }
  83. */
  84. else if (scrollHeight > windowHeight &&
  85. (body.offsetHeight <= windowHeight ||
  86. html.offsetHeight <= windowHeight)) {
  87. html.style.height = 'auto';
  88. //setTimeout(refresh, 10);
  89. // clearfix
  90. if (root.offsetHeight <= windowHeight) {
  91. var underlay = document.createElement("div");
  92. underlay.style.clear = "both";
  93. body.appendChild(underlay);
  94. }
  95. }
  96. // disable fixed background
  97. if (!options.fixedBackground && !isExcluded) {
  98. body.style.backgroundAttachment = "scroll";
  99. html.style.backgroundAttachment = "scroll";
  100. }
  101. }
  102. /************************************************
  103. * SCROLLING
  104. ************************************************/
  105. var que = [];
  106. var pending = false;
  107. var lastScroll = +new Date;
  108. /**
  109. * Pushes scroll actions to the scrolling queue.
  110. */
  111. function scrollArray(elem, left, top, delay) {
  112. delay || (delay = 1000);
  113. directionCheck(left, top);
  114. if (options.accelerationMax != 1) {
  115. var now = +new Date;
  116. var elapsed = now - lastScroll;
  117. if (elapsed < options.accelerationDelta) {
  118. var factor = (1 + (30 / elapsed)) / 2;
  119. if (factor > 1) {
  120. factor = Math.min(factor, options.accelerationMax);
  121. left *= factor;
  122. top *= factor;
  123. }
  124. }
  125. lastScroll = +new Date;
  126. }
  127. // push a scroll command
  128. que.push({
  129. x: left,
  130. y: top,
  131. lastX: (left < 0) ? 0.99 : -0.99,
  132. lastY: (top < 0) ? 0.99 : -0.99,
  133. start: +new Date
  134. });
  135. // don't act if there's a pending queue
  136. if (pending) {
  137. return;
  138. }
  139. var scrollWindow = (elem === document.body);
  140. var step = function (time) {
  141. var now = +new Date;
  142. var scrollX = 0;
  143. var scrollY = 0;
  144. for (var i = 0; i < que.length; i++) {
  145. var item = que[i];
  146. var elapsed = now - item.start;
  147. var finished = (elapsed >= options.animationTime);
  148. // scroll position: [0, 1]
  149. var position = (finished) ? 1 : elapsed / options.animationTime;
  150. // easing [optional]
  151. if (options.pulseAlgorithm) {
  152. position = pulse(position);
  153. }
  154. // only need the difference
  155. var x = (item.x * position - item.lastX) >> 0;
  156. var y = (item.y * position - item.lastY) >> 0;
  157. // add this to the total scrolling
  158. scrollX += x;
  159. scrollY += y;
  160. // update last values
  161. item.lastX += x;
  162. item.lastY += y;
  163. // delete and step back if it's over
  164. if (finished) {
  165. que.splice(i, 1); i--;
  166. }
  167. }
  168. // scroll left and top
  169. if (scrollWindow) {
  170. window.scrollBy(scrollX, scrollY);
  171. }
  172. else {
  173. if (scrollX) elem.scrollLeft += scrollX;
  174. if (scrollY) elem.scrollTop += scrollY;
  175. }
  176. // clean up if there's nothing left to do
  177. if (!left && !top) {
  178. que = [];
  179. }
  180. if (que.length) {
  181. requestFrame(step, elem, (delay / options.frameRate + 1));
  182. } else {
  183. pending = false;
  184. }
  185. };
  186. // start a new queue of actions
  187. requestFrame(step, elem, 0);
  188. pending = true;
  189. }
  190. /***********************************************
  191. * EVENTS
  192. ***********************************************/
  193. /**
  194. * Mouse wheel handler.
  195. * @param {Object} event
  196. */
  197. function wheel(event) {
  198. if (!initDone) {
  199. init();
  200. }
  201. var target = event.target;
  202. var overflowing = overflowingAncestor(target);
  203. // use default if there's no overflowing
  204. // element or default action is prevented
  205. if (!overflowing || event.defaultPrevented ||
  206. isNodeName(activeElement, "embed") ||
  207. (isNodeName(target, "embed") && /\.pdf/i.test(target.src))) {
  208. return true;
  209. }
  210. var deltaX = event.wheelDeltaX || 0;
  211. var deltaY = event.wheelDeltaY || 0;
  212. // use wheelDelta if deltaX/Y is not available
  213. if (!deltaX && !deltaY) {
  214. deltaY = event.wheelDelta || 0;
  215. }
  216. // check if it's a touchpad scroll that should be ignored
  217. if (!options.touchpadSupport && isTouchpad(deltaY)) {
  218. return true;
  219. }
  220. // scale by step size
  221. // delta is 120 most of the time
  222. // synaptics seems to send 1 sometimes
  223. if (Math.abs(deltaX) > 1.2) {
  224. deltaX *= options.stepSize / 120;
  225. }
  226. if (Math.abs(deltaY) > 1.2) {
  227. deltaY *= options.stepSize / 120;
  228. }
  229. scrollArray(overflowing, -deltaX, -deltaY);
  230. event.preventDefault();
  231. }
  232. /**
  233. * Keydown event handler.
  234. * @param {Object} event
  235. */
  236. function keydown(event) {
  237. var target = event.target;
  238. var modifier = event.ctrlKey || event.altKey || event.metaKey ||
  239. (event.shiftKey && event.keyCode !== key.spacebar);
  240. // do nothing if user is editing text
  241. // or using a modifier key (except shift)
  242. // or in a dropdown
  243. if ( /input|textarea|select|embed/i.test(target.nodeName) ||
  244. target.isContentEditable ||
  245. event.defaultPrevented ||
  246. modifier ) {
  247. return true;
  248. }
  249. // spacebar should trigger button press
  250. if (isNodeName(target, "button") &&
  251. event.keyCode === key.spacebar) {
  252. return true;
  253. }
  254. var shift, x = 0, y = 0;
  255. var elem = overflowingAncestor(activeElement);
  256. var clientHeight = elem.clientHeight;
  257. if (elem == document.body) {
  258. clientHeight = window.innerHeight;
  259. }
  260. switch (event.keyCode) {
  261. case key.up:
  262. y = -options.arrowScroll;
  263. break;
  264. case key.down:
  265. y = options.arrowScroll;
  266. break;
  267. case key.spacebar: // (+ shift)
  268. shift = event.shiftKey ? 1 : -1;
  269. y = -shift * clientHeight * 0.9;
  270. break;
  271. case key.pageup:
  272. y = -clientHeight * 0.9;
  273. break;
  274. case key.pagedown:
  275. y = clientHeight * 0.9;
  276. break;
  277. case key.home:
  278. y = -elem.scrollTop;
  279. break;
  280. case key.end:
  281. var damt = elem.scrollHeight - elem.scrollTop - clientHeight;
  282. y = (damt > 0) ? damt+10 : 0;
  283. break;
  284. case key.left:
  285. x = -options.arrowScroll;
  286. break;
  287. case key.right:
  288. x = options.arrowScroll;
  289. break;
  290. default:
  291. return true; // a key we don't care about
  292. }
  293. scrollArray(elem, x, y);
  294. event.preventDefault();
  295. }
  296. /**
  297. * Mousedown event only for updating activeElement
  298. */
  299. function mousedown(event) {
  300. activeElement = event.target;
  301. }
  302. /***********************************************
  303. * OVERFLOW
  304. ***********************************************/
  305. var cache = {}; // cleared out every once in while
  306. setInterval(function () { cache = {}; }, 10 * 1000);
  307. var uniqueID = (function () {
  308. var i = 0;
  309. return function (el) {
  310. return el.uniqueID || (el.uniqueID = i++);
  311. };
  312. })();
  313. function setCache(elems, overflowing) {
  314. for (var i = elems.length; i--;)
  315. cache[uniqueID(elems[i])] = overflowing;
  316. return overflowing;
  317. }
  318. function overflowingAncestor(el) {
  319. var elems = [];
  320. var rootScrollHeight = root.scrollHeight;
  321. do {
  322. var cached = cache[uniqueID(el)];
  323. if (cached) {
  324. return setCache(elems, cached);
  325. }
  326. elems.push(el);
  327. if (rootScrollHeight === el.scrollHeight) {
  328. if (!isFrame || root.clientHeight + 10 < rootScrollHeight) {
  329. return setCache(elems, document.body); // scrolling root in WebKit
  330. }
  331. } else if (el.clientHeight + 10 < el.scrollHeight) {
  332. overflow = getComputedStyle(el, "").getPropertyValue("overflow-y");
  333. if (overflow === "scroll" || overflow === "auto") {
  334. return setCache(elems, el);
  335. }
  336. }
  337. } while (el = el.parentNode);
  338. }
  339. /***********************************************
  340. * HELPERS
  341. ***********************************************/
  342. function addEvent(type, fn, bubble) {
  343. window.addEventListener(type, fn, (bubble||false));
  344. }
  345. function removeEvent(type, fn, bubble) {
  346. window.removeEventListener(type, fn, (bubble||false));
  347. }
  348. function isNodeName(el, tag) {
  349. return (el.nodeName||"").toLowerCase() === tag.toLowerCase();
  350. }
  351. function directionCheck(x, y) {
  352. x = (x > 0) ? 1 : -1;
  353. y = (y > 0) ? 1 : -1;
  354. if (direction.x !== x || direction.y !== y) {
  355. direction.x = x;
  356. direction.y = y;
  357. que = [];
  358. lastScroll = 0;
  359. }
  360. }
  361. var deltaBufferTimer;
  362. function isTouchpad(deltaY) {
  363. if (!deltaY) return;
  364. deltaY = Math.abs(deltaY)
  365. deltaBuffer.push(deltaY);
  366. deltaBuffer.shift();
  367. clearTimeout(deltaBufferTimer);
  368. var allEquals = (deltaBuffer[0] == deltaBuffer[1] &&
  369. deltaBuffer[1] == deltaBuffer[2]);
  370. var allDivisable = (isDivisible(deltaBuffer[0], 120) &&
  371. isDivisible(deltaBuffer[1], 120) &&
  372. isDivisible(deltaBuffer[2], 120));
  373. return !(allEquals || allDivisable);
  374. }
  375. function isDivisible(n, divisor) {
  376. return (Math.floor(n / divisor) == n / divisor);
  377. }
  378. var requestFrame = (function () {
  379. return window.requestAnimationFrame ||
  380. window.webkitRequestAnimationFrame ||
  381. function (callback, element, delay) {
  382. window.setTimeout(callback, delay || (1000/60));
  383. };
  384. })();
  385. /***********************************************
  386. * PULSE
  387. ***********************************************/
  388. /**
  389. * Viscous fluid with a pulse for part and decay for the rest.
  390. * - Applies a fixed force over an interval (a damped acceleration), and
  391. * - Lets the exponential bleed away the velocity over a longer interval
  392. * - Michael Herf, http://stereopsis.com/stopping/
  393. */
  394. function pulse_(x) {
  395. var val, start, expx;
  396. // test
  397. x = x * options.pulseScale;
  398. if (x < 1) { // acceleartion
  399. val = x - (1 - Math.exp(-x));
  400. } else { // tail
  401. // the previous animation ended here:
  402. start = Math.exp(-1);
  403. // simple viscous drag
  404. x -= 1;
  405. expx = 1 - Math.exp(-x);
  406. val = start + (expx * (1 - start));
  407. }
  408. return val * options.pulseNormalize;
  409. }
  410. function pulse(x) {
  411. if (x >= 1) return 1;
  412. if (x <= 0) return 0;
  413. if (options.pulseNormalize == 1) {
  414. options.pulseNormalize /= pulse_(1);
  415. }
  416. return pulse_(x);
  417. }
  418. var isChrome = /chrome/i.test(window.navigator.userAgent);
  419. var isMouseWheelSupported = 'onmousewheel' in document;
  420. if (isMouseWheelSupported && isChrome) {
  421. addEvent("mousedown", mousedown);
  422. addEvent("mousewheel", wheel);
  423. addEvent("load", init);
  424. };
  425. })();