123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536 |
- // SmoothScroll for websites v1.2.1
- // Licensed under the terms of the MIT license.
- // https://gist.github.com/galambalazs/6477177/
- // 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);
- }
-
- })();
|