Blame | Letzte Änderung | Log anzeigen | RSS feed
// 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 CoreframeRate : 150, // [Hz]animationTime : 400, // [px]stepSize : 120, // [px]// Pulse (less tweakable)// ratio of "tail" to "acceleration"pulseAlgorithm : true,pulseScale : 8,pulseNormalize : 1,// AccelerationaccelerationDelta : 20, // 20accelerationMax : 1, // 1// Keyboard SettingskeyboardSupport : true, // optionarrowScroll : 50, // [px]// OthertouchpadSupport : true,fixedBackground : true,excluded : ""};var options = defaultOptions;// Other Variablesvar 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 itif (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 elementroot = (document.compatMode.indexOf('CSS') >= 0) ? html : body;activeElement = body;initTest();initDone = true;// Checks if this script is running in a frameif (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 heightvar pending = false;var refresh = function () {if (!pending && html.scrollHeight != document.height) {pending = true; // add a new pending actionsetTimeout(function () {html.style.height = document.height + 'px';pending = false;}, 500); // act rarely to stay fast}};html.style.height = 'auto';setTimeout(refresh, 10);// clearfixif (root.offsetHeight <= windowHeight) {var underlay = document.createElement("div");underlay.style.clear = "both";body.appendChild(underlay);}}// disable fixed backgroundif (!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 commandque.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 queueif (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 differencevar x = (item.x * position - item.lastX) >> 0;var y = (item.y * position - item.lastY) >> 0;// add this to the total scrollingscrollX += x;scrollY += y;// update last valuesitem.lastX += x;item.lastY += y;// delete and step back if it's overif (finished) {que.splice(i, 1); i--;}}// scroll left and topif (scrollWindow) {window.scrollBy(scrollX, scrollY);}else {if (scrollX) elem.scrollLeft += scrollX;if (scrollY) elem.scrollTop += scrollY;}// clean up if there's nothing left to doif (!left && !top) {que = [];}if (que.length) {requestFrame(step, elem, (delay / options.frameRate + 1));} else {pending = false;}};// start a new queue of actionsrequestFrame(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 preventedif (!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 availableif (!deltaX && !deltaY) {deltaY = event.wheelDelta || 0;}// check if it's a touchpad scroll that should be ignoredif (!options.touchpadSupport && isTouchpad(deltaY)) {return true;}// scale by step size// delta is 120 most of the time// synaptics seems to send 1 sometimesif (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 dropdownif ( /input|textarea|select|embed/i.test(target.nodeName) ||target.isContentEditable ||event.defaultPrevented ||modifier ) {return true;}// spacebar should trigger button pressif (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 whilesetInterval(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;// testx = x * options.pulseScale;if (x < 1) { // acceleartionval = x - (1 - Math.exp(-x));} else { // tail// the previous animation ended here:start = Math.exp(-1);// simple viscous dragx -= 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);}})();