mirror of
				https://github.com/dawidolko/Website-Templates.git
				synced 2025-10-28 16:03:10 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			535 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			535 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| // SmoothScroll for websites v1.2.1
 | |
| // Licensed under the terms of the MIT license.
 | |
| 
 | |
| // People involved
 | |
| //  - Balazs Galambosi (maintainer)  
 | |
| //  - Michael Herf     (Pulse Algorithm)
 | |
| 
 | |
| (function(){
 | |
|   
 | |
| // Scroll Variables (tweakable)
 | |
| var defaultOptions = {
 | |
| 
 | |
|     // Scrolling Core
 | |
|     frameRate        : 150, // [Hz]
 | |
|     animationTime    : 400, // [px]
 | |
|     stepSize         : 120, // [px]
 | |
| 
 | |
|     // Pulse (less tweakable)
 | |
|     // ratio of "tail" to "acceleration"
 | |
|     pulseAlgorithm   : true,
 | |
|     pulseScale       : 8,
 | |
|     pulseNormalize   : 1,
 | |
| 
 | |
|     // Acceleration
 | |
|     accelerationDelta : 20,  // 20
 | |
|     accelerationMax   : 1,   // 1
 | |
| 
 | |
|     // Keyboard Settings
 | |
|     keyboardSupport   : true,  // option
 | |
|     arrowScroll       : 50,     // [px]
 | |
| 
 | |
|     // Other
 | |
|     touchpadSupport   : true,
 | |
|     fixedBackground   : true, 
 | |
|     excluded          : ""    
 | |
| };
 | |
| 
 | |
| var options = defaultOptions;
 | |
| 
 | |
| 
 | |
| // Other Variables
 | |
| var isExcluded = false;
 | |
| var isFrame = false;
 | |
| var direction = { x: 0, y: 0 };
 | |
| var initDone  = false;
 | |
| var root = document.documentElement;
 | |
| var activeElement;
 | |
| var observer;
 | |
| var deltaBuffer = [ 120, 120, 120 ];
 | |
| 
 | |
| var key = { left: 37, up: 38, right: 39, down: 40, spacebar: 32, 
 | |
|             pageup: 33, pagedown: 34, end: 35, home: 36 };
 | |
| 
 | |
| 
 | |
| /***********************************************
 | |
|  * SETTINGS
 | |
|  ***********************************************/
 | |
| 
 | |
| var options = defaultOptions;
 | |
| 
 | |
| 
 | |
| /***********************************************
 | |
|  * INITIALIZE
 | |
|  ***********************************************/
 | |
| 
 | |
| /**
 | |
|  * Tests if smooth scrolling is allowed. Shuts down everything if not.
 | |
|  */
 | |
| function initTest() {
 | |
| 
 | |
|     var disableKeyboard = false; 
 | |
|     
 | |
|     // disable keyboard support if anything above requested it
 | |
|     if (disableKeyboard) {
 | |
|         removeEvent("keydown", keydown);
 | |
|     }
 | |
| 
 | |
|     if (options.keyboardSupport && !disableKeyboard) {
 | |
|         addEvent("keydown", keydown);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Sets up scrolls array, determines if frames are involved.
 | |
|  */
 | |
| function init() {
 | |
|   
 | |
|     if (!document.body) return;
 | |
| 
 | |
|     var body = document.body;
 | |
|     var html = document.documentElement;
 | |
|     var windowHeight = window.innerHeight; 
 | |
|     var scrollHeight = body.scrollHeight;
 | |
|     
 | |
|     // check compat mode for root element
 | |
|     root = (document.compatMode.indexOf('CSS') >= 0) ? html : body;
 | |
|     activeElement = body;
 | |
|     
 | |
|     initTest();
 | |
|     initDone = true;
 | |
| 
 | |
|     // Checks if this script is running in a frame
 | |
|     if (top != self) {
 | |
|         isFrame = true;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * This fixes a bug where the areas left and right to 
 | |
|      * the content does not trigger the onmousewheel event
 | |
|      * on some pages. e.g.: html, body { height: 100% }
 | |
|      */
 | |
|     else if (scrollHeight > windowHeight &&
 | |
|             (body.offsetHeight <= windowHeight || 
 | |
|              html.offsetHeight <= windowHeight)) {
 | |
| 
 | |
|         // DOMChange (throttle): fix height
 | |
|         var pending = false;
 | |
|         var refresh = function () {
 | |
|             if (!pending && html.scrollHeight != document.height) {
 | |
|                 pending = true; // add a new pending action
 | |
|                 setTimeout(function () {
 | |
|                     html.style.height = document.height + 'px';
 | |
|                     pending = false;
 | |
|                 }, 500); // act rarely to stay fast
 | |
|             }
 | |
|         };
 | |
|         html.style.height = 'auto';
 | |
|         setTimeout(refresh, 10);
 | |
| 
 | |
|         // clearfix
 | |
|         if (root.offsetHeight <= windowHeight) {
 | |
|             var underlay = document.createElement("div"); 	
 | |
|             underlay.style.clear = "both";
 | |
|             body.appendChild(underlay);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // disable fixed background
 | |
|     if (!options.fixedBackground && !isExcluded) {
 | |
|         body.style.backgroundAttachment = "scroll";
 | |
|         html.style.backgroundAttachment = "scroll";
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| /************************************************
 | |
|  * SCROLLING 
 | |
|  ************************************************/
 | |
|  
 | |
| var que = [];
 | |
| var pending = false;
 | |
| var lastScroll = +new Date;
 | |
| 
 | |
| /**
 | |
|  * Pushes scroll actions to the scrolling queue.
 | |
|  */
 | |
| function scrollArray(elem, left, top, delay) {
 | |
|     
 | |
|     delay || (delay = 1000);
 | |
|     directionCheck(left, top);
 | |
| 
 | |
|     if (options.accelerationMax != 1) {
 | |
|         var now = +new Date;
 | |
|         var elapsed = now - lastScroll;
 | |
|         if (elapsed < options.accelerationDelta) {
 | |
|             var factor = (1 + (30 / elapsed)) / 2;
 | |
|             if (factor > 1) {
 | |
|                 factor = Math.min(factor, options.accelerationMax);
 | |
|                 left *= factor;
 | |
|                 top  *= factor;
 | |
|             }
 | |
|         }
 | |
|         lastScroll = +new Date;
 | |
|     }          
 | |
|     
 | |
|     // push a scroll command
 | |
|     que.push({
 | |
|         x: left, 
 | |
|         y: top, 
 | |
|         lastX: (left < 0) ? 0.99 : -0.99,
 | |
|         lastY: (top  < 0) ? 0.99 : -0.99, 
 | |
|         start: +new Date
 | |
|     });
 | |
|         
 | |
|     // don't act if there's a pending queue
 | |
|     if (pending) {
 | |
|         return;
 | |
|     }  
 | |
| 
 | |
|     var scrollWindow = (elem === document.body);
 | |
|     
 | |
|     var step = function (time) {
 | |
|         
 | |
|         var now = +new Date;
 | |
|         var scrollX = 0;
 | |
|         var scrollY = 0; 
 | |
|     
 | |
|         for (var i = 0; i < que.length; i++) {
 | |
|             
 | |
|             var item = que[i];
 | |
|             var elapsed  = now - item.start;
 | |
|             var finished = (elapsed >= options.animationTime);
 | |
|             
 | |
|             // scroll position: [0, 1]
 | |
|             var position = (finished) ? 1 : elapsed / options.animationTime;
 | |
|             
 | |
|             // easing [optional]
 | |
|             if (options.pulseAlgorithm) {
 | |
|                 position = pulse(position);
 | |
|             }
 | |
|             
 | |
|             // only need the difference
 | |
|             var x = (item.x * position - item.lastX) >> 0;
 | |
|             var y = (item.y * position - item.lastY) >> 0;
 | |
|             
 | |
|             // add this to the total scrolling
 | |
|             scrollX += x;
 | |
|             scrollY += y;            
 | |
|             
 | |
|             // update last values
 | |
|             item.lastX += x;
 | |
|             item.lastY += y;
 | |
|         
 | |
|             // delete and step back if it's over
 | |
|             if (finished) {
 | |
|                 que.splice(i, 1); i--;
 | |
|             }           
 | |
|         }
 | |
| 
 | |
|         // scroll left and top
 | |
|         if (scrollWindow) {
 | |
|             window.scrollBy(scrollX, scrollY);
 | |
|         } 
 | |
|         else {
 | |
|             if (scrollX) elem.scrollLeft += scrollX;
 | |
|             if (scrollY) elem.scrollTop  += scrollY;                    
 | |
|         }
 | |
|         
 | |
|         // clean up if there's nothing left to do
 | |
|         if (!left && !top) {
 | |
|             que = [];
 | |
|         }
 | |
|         
 | |
|         if (que.length) { 
 | |
|             requestFrame(step, elem, (delay / options.frameRate + 1)); 
 | |
|         } else { 
 | |
|             pending = false;
 | |
|         }
 | |
|     };
 | |
|     
 | |
|     // start a new queue of actions
 | |
|     requestFrame(step, elem, 0);
 | |
|     pending = true;
 | |
| }
 | |
| 
 | |
| 
 | |
| /***********************************************
 | |
|  * EVENTS
 | |
|  ***********************************************/
 | |
| 
 | |
| /**
 | |
|  * Mouse wheel handler.
 | |
|  * @param {Object} event
 | |
|  */
 | |
| function wheel(event) {
 | |
| 
 | |
|     if (!initDone) {
 | |
|         init();
 | |
|     }
 | |
|     
 | |
|     var target = event.target;
 | |
|     var overflowing = overflowingAncestor(target);
 | |
|     
 | |
|     // use default if there's no overflowing
 | |
|     // element or default action is prevented    
 | |
|     if (!overflowing || event.defaultPrevented ||
 | |
|         isNodeName(activeElement, "embed") ||
 | |
|        (isNodeName(target, "embed") && /\.pdf/i.test(target.src))) {
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     var deltaX = event.wheelDeltaX || 0;
 | |
|     var deltaY = event.wheelDeltaY || 0;
 | |
|     
 | |
|     // use wheelDelta if deltaX/Y is not available
 | |
|     if (!deltaX && !deltaY) {
 | |
|         deltaY = event.wheelDelta || 0;
 | |
|     }
 | |
| 
 | |
|     // check if it's a touchpad scroll that should be ignored
 | |
|     if (!options.touchpadSupport && isTouchpad(deltaY)) {
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     // scale by step size
 | |
|     // delta is 120 most of the time
 | |
|     // synaptics seems to send 1 sometimes
 | |
|     if (Math.abs(deltaX) > 1.2) {
 | |
|         deltaX *= options.stepSize / 120;
 | |
|     }
 | |
|     if (Math.abs(deltaY) > 1.2) {
 | |
|         deltaY *= options.stepSize / 120;
 | |
|     }
 | |
|     
 | |
|     scrollArray(overflowing, -deltaX, -deltaY);
 | |
|     event.preventDefault();
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Keydown event handler.
 | |
|  * @param {Object} event
 | |
|  */
 | |
| function keydown(event) {
 | |
| 
 | |
|     var target   = event.target;
 | |
|     var modifier = event.ctrlKey || event.altKey || event.metaKey || 
 | |
|                   (event.shiftKey && event.keyCode !== key.spacebar);
 | |
|     
 | |
|     // do nothing if user is editing text
 | |
|     // or using a modifier key (except shift)
 | |
|     // or in a dropdown
 | |
|     if ( /input|textarea|select|embed/i.test(target.nodeName) ||
 | |
|          target.isContentEditable || 
 | |
|          event.defaultPrevented   ||
 | |
|          modifier ) {
 | |
|       return true;
 | |
|     }
 | |
|     // spacebar should trigger button press
 | |
|     if (isNodeName(target, "button") &&
 | |
|         event.keyCode === key.spacebar) {
 | |
|       return true;
 | |
|     }
 | |
|     
 | |
|     var shift, x = 0, y = 0;
 | |
|     var elem = overflowingAncestor(activeElement);
 | |
|     var clientHeight = elem.clientHeight;
 | |
| 
 | |
|     if (elem == document.body) {
 | |
|         clientHeight = window.innerHeight;
 | |
|     }
 | |
| 
 | |
|     switch (event.keyCode) {
 | |
|         case key.up:
 | |
|             y = -options.arrowScroll;
 | |
|             break;
 | |
|         case key.down:
 | |
|             y = options.arrowScroll;
 | |
|             break;         
 | |
|         case key.spacebar: // (+ shift)
 | |
|             shift = event.shiftKey ? 1 : -1;
 | |
|             y = -shift * clientHeight * 0.9;
 | |
|             break;
 | |
|         case key.pageup:
 | |
|             y = -clientHeight * 0.9;
 | |
|             break;
 | |
|         case key.pagedown:
 | |
|             y = clientHeight * 0.9;
 | |
|             break;
 | |
|         case key.home:
 | |
|             y = -elem.scrollTop;
 | |
|             break;
 | |
|         case key.end:
 | |
|             var damt = elem.scrollHeight - elem.scrollTop - clientHeight;
 | |
|             y = (damt > 0) ? damt+10 : 0;
 | |
|             break;
 | |
|         case key.left:
 | |
|             x = -options.arrowScroll;
 | |
|             break;
 | |
|         case key.right:
 | |
|             x = options.arrowScroll;
 | |
|             break;            
 | |
|         default:
 | |
|             return true; // a key we don't care about
 | |
|     }
 | |
| 
 | |
|     scrollArray(elem, x, y);
 | |
|     event.preventDefault();
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Mousedown event only for updating activeElement
 | |
|  */
 | |
| function mousedown(event) {
 | |
|     activeElement = event.target;
 | |
| }
 | |
| 
 | |
| 
 | |
| /***********************************************
 | |
|  * OVERFLOW
 | |
|  ***********************************************/
 | |
|  
 | |
| var cache = {}; // cleared out every once in while
 | |
| setInterval(function () { cache = {}; }, 10 * 1000);
 | |
| 
 | |
| var uniqueID = (function () {
 | |
|     var i = 0;
 | |
|     return function (el) {
 | |
|         return el.uniqueID || (el.uniqueID = i++);
 | |
|     };
 | |
| })();
 | |
| 
 | |
| function setCache(elems, overflowing) {
 | |
|     for (var i = elems.length; i--;)
 | |
|         cache[uniqueID(elems[i])] = overflowing;
 | |
|     return overflowing;
 | |
| }
 | |
| 
 | |
| function overflowingAncestor(el) {
 | |
|     var elems = [];
 | |
|     var rootScrollHeight = root.scrollHeight;
 | |
|     do {
 | |
|         var cached = cache[uniqueID(el)];
 | |
|         if (cached) {
 | |
|             return setCache(elems, cached);
 | |
|         }
 | |
|         elems.push(el);
 | |
|         if (rootScrollHeight === el.scrollHeight) {
 | |
|             if (!isFrame || root.clientHeight + 10 < rootScrollHeight) {
 | |
|                 return setCache(elems, document.body); // scrolling root in WebKit
 | |
|             }
 | |
|         } else if (el.clientHeight + 10 < el.scrollHeight) {
 | |
|             overflow = getComputedStyle(el, "").getPropertyValue("overflow-y");
 | |
|             if (overflow === "scroll" || overflow === "auto") {
 | |
|                 return setCache(elems, el);
 | |
|             }
 | |
|         }
 | |
|     } while (el = el.parentNode);
 | |
| }
 | |
| 
 | |
| 
 | |
| /***********************************************
 | |
|  * HELPERS
 | |
|  ***********************************************/
 | |
| 
 | |
| function addEvent(type, fn, bubble) {
 | |
|     window.addEventListener(type, fn, (bubble||false));
 | |
| }
 | |
| 
 | |
| function removeEvent(type, fn, bubble) {
 | |
|     window.removeEventListener(type, fn, (bubble||false));  
 | |
| }
 | |
| 
 | |
| function isNodeName(el, tag) {
 | |
|     return (el.nodeName||"").toLowerCase() === tag.toLowerCase();
 | |
| }
 | |
| 
 | |
| function directionCheck(x, y) {
 | |
|     x = (x > 0) ? 1 : -1;
 | |
|     y = (y > 0) ? 1 : -1;
 | |
|     if (direction.x !== x || direction.y !== y) {
 | |
|         direction.x = x;
 | |
|         direction.y = y;
 | |
|         que = [];
 | |
|         lastScroll = 0;
 | |
|     }
 | |
| }
 | |
| 
 | |
| var deltaBufferTimer;
 | |
| 
 | |
| function isTouchpad(deltaY) {
 | |
|     if (!deltaY) return;
 | |
|     deltaY = Math.abs(deltaY)
 | |
|     deltaBuffer.push(deltaY);
 | |
|     deltaBuffer.shift();
 | |
|     clearTimeout(deltaBufferTimer);
 | |
|     var allDivisable = (isDivisible(deltaBuffer[0], 120) &&
 | |
|                         isDivisible(deltaBuffer[1], 120) &&
 | |
|                         isDivisible(deltaBuffer[2], 120));
 | |
|     return !allDivisable;
 | |
| } 
 | |
| 
 | |
| function isDivisible(n, divisor) {
 | |
|     return (Math.floor(n / divisor) == n / divisor);
 | |
| }
 | |
| 
 | |
| var requestFrame = (function () {
 | |
|       return  window.requestAnimationFrame       || 
 | |
|               window.webkitRequestAnimationFrame || 
 | |
|               function (callback, element, delay) {
 | |
|                   window.setTimeout(callback, delay || (1000/60));
 | |
|               };
 | |
| })();
 | |
| 
 | |
| 
 | |
| /***********************************************
 | |
|  * PULSE
 | |
|  ***********************************************/
 | |
|  
 | |
| /**
 | |
|  * Viscous fluid with a pulse for part and decay for the rest.
 | |
|  * - Applies a fixed force over an interval (a damped acceleration), and
 | |
|  * - Lets the exponential bleed away the velocity over a longer interval
 | |
|  * - Michael Herf, http://stereopsis.com/stopping/
 | |
|  */
 | |
| function pulse_(x) {
 | |
|     var val, start, expx;
 | |
|     // test
 | |
|     x = x * options.pulseScale;
 | |
|     if (x < 1) { // acceleartion
 | |
|         val = x - (1 - Math.exp(-x));
 | |
|     } else {     // tail
 | |
|         // the previous animation ended here:
 | |
|         start = Math.exp(-1);
 | |
|         // simple viscous drag
 | |
|         x -= 1;
 | |
|         expx = 1 - Math.exp(-x);
 | |
|         val = start + (expx * (1 - start));
 | |
|     }
 | |
|     return val * options.pulseNormalize;
 | |
| }
 | |
| 
 | |
| function pulse(x) {
 | |
|     if (x >= 1) return 1;
 | |
|     if (x <= 0) return 0;
 | |
| 
 | |
|     if (options.pulseNormalize == 1) {
 | |
|         options.pulseNormalize /= pulse_(1);
 | |
|     }
 | |
|     return pulse_(x);
 | |
| }
 | |
| 
 | |
| var isChrome = /chrome/i.test(window.navigator.userAgent);
 | |
| var wheelEvent = null;
 | |
| if ("onwheel" in document.createElement("div"))
 | |
| 	wheelEvent = "wheel";
 | |
| else if ("onmousewheel" in document.createElement("div"))
 | |
| 	wheelEvent = "mousewheel";
 | |
| 
 | |
| if (wheelEvent && isChrome) {
 | |
| 	addEvent(wheelEvent, wheel);
 | |
| 	addEvent("mousedown", mousedown);
 | |
| 	addEvent("load", init);
 | |
| }
 | |
| 
 | |
| })(); |