Team:Worldshaper-Shanghai/js/T--Worldshaper-Shanghai--SmoothScroll.minT--Worldshaper-Shanghai--SmoothScroll.min.js

// 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    : 1800, // [px]
   stepSize         : 85, // [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)) {
       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 allEquals    = (deltaBuffer[0] == deltaBuffer[1] && 
                       deltaBuffer[1] == deltaBuffer[2]);
   var allDivisable = (isDivisible(deltaBuffer[0], 120) &&
                       isDivisible(deltaBuffer[1], 120) &&
                       isDivisible(deltaBuffer[2], 120));
   return !(allEquals || 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 isMouseWheelSupported = 'onmousewheel' in document;

if (isMouseWheelSupported && isChrome) { addEvent("mousedown", mousedown); addEvent("mousewheel", wheel); addEvent("load", init); };

})();