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

Paste is for source code and general debugging text.

Login or Register to edit, delete and keep track of your pastes and more.

Raw Paste

Login or Register to edit or fork this paste. It's free.