smooth scroll
/* SmoothScroll v0.9.9
Licensed under the terms of the MIT license.
People involved
- Balazs Galambosi: maintainer (CHANGELOG.txt)
- Patrick Brunner (patrickb1991@gmail.com)
- Michael Herf: ssc_pulse Algorithm
*/
jQuery(document).ready(function($) {
// Scroll Variables (tweakable)
var ssc_framerate = 150; // [Hz]
var ssc_animtime = 700; // [px]
var ssc_stepsize = 170; // [px]
// ssc_pulse (less tweakable)
// ratio of "tail" to "acceleration"
var ssc_pulseAlgorithm = true;
var ssc_pulseScale = 6;
var ssc_pulseNormalize = 1;
// Keyboard Settings
var ssc_keyboardsupport = true;
var ssc_arrowscroll = 60; // [px]
// Other Variables
var ssc_frame = false;
var ssc_direction = { x: 0, y: 0 };
var ssc_initdone = false;
var ssc_fixedback = true;
var ssc_root = document.documentElement;
var ssc_activeElement;
var ssc_key = { left: 37, up: 38, right: 39, down: 40, spacebar: 32, pageup: 33, pagedown: 34, end: 35, home: 36 };
/***********************************************
* INITIALIZE
***********************************************/
/**
* Sets up scrolls array, determines if ssc_frames are involved.
*/
function ssc_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 ssc_root element
ssc_root = (document.compatMode.indexOf('CSS') >= 0) ? html : body;
ssc_activeElement = body;
ssc_initdone = true;
// Checks if this script is running in a ssc_frame
if (top != self) { ssc_frame = 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)) {
ssc_root.style.height = "auto";
if (ssc_root.offsetHeight <= windowHeight) {
var underlay = document.createElement("div");
underlay.style.clear = "both";
body.appendChild(underlay);
}
}
if (!ssc_fixedback) {
body.style.backgroundAttachment = "scroll";
html.style.backgroundAttachment = "scroll";
}
if (ssc_keyboardsupport) {
ssc_addEvent("keydown", ssc_keydown);
}
}
/************************************************
* SCROLLING
************************************************/
var ssc_que = [];
var ssc_pending = false;
/**
* Pushes scroll actions to the scrolling queue.
*/
function ssc_scrollArray(elem, left, top, delay) {
delay || (delay = 1000);
ssc_directionCheck(left, top);
// push a scroll command
ssc_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 ssc_pending queue
if (ssc_pending) { return; }
var step = function() {
var now = +new Date;
var scrollX = 0;
var scrollY = 0;
for (var i = 0; i < ssc_que.length; i++) {
var item = ssc_que[i];
var elapsed = now - item.start;
var finished = (elapsed >= ssc_animtime);
// scroll position: [0, 1]
var position = (finished) ? 1 : elapsed / ssc_animtime;
// easing [optional]
if (ssc_pulseAlgorithm) { position = ssc_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) { ssc_que.splice(i, 1); i--; }
}
// scroll left
if (left) {
var lastLeft = elem.scrollLeft;
elem.scrollLeft += scrollX;
// scroll left failed (edge)
if (scrollX && elem.scrollLeft === lastLeft) { left = 0; }
}
// scroll top
if (top) {
var lastTop = elem.scrollTop;
elem.scrollTop += scrollY;
// scroll top failed (edge)
if (scrollY && elem.scrollTop === lastTop) { top = 0; }
}
// clean up if there's nothing left to do
if (!left && !top) { ssc_que = []; }
if (ssc_que.length) {
setTimeout(step, delay / ssc_framerate + 1);
}
else { ssc_pending = false; }
}
// start a new queue of actions
setTimeout(step, 0);
ssc_pending = true;
}
/***********************************************
* EVENTS
***********************************************/
/**
* Mouse ssc_wheel handler.
* @param {Object} event
*/
function ssc_wheel(event) {
if (!ssc_initdone) {
init();
}
var target = event.target;
var overflowing = ssc_overflowingAncestor(target);
// use default if there's no overflowing
// element or default action is prevented
if (!overflowing || event.defaultPrevented ||
ssc_isNodeName(ssc_activeElement, "embed") ||
(ssc_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;
}
// scale by step size
// delta is 120 most of the time
// synaptics seems to send 1 sometimes
if (Math.abs(deltaX) > 1.2) {
deltaX *= ssc_stepsize / 120;
}
if (Math.abs(deltaY) > 1.2) {
deltaY *= ssc_stepsize / 120;
}
ssc_scrollArray(overflowing, -deltaX, -deltaY);
event.preventDefault();
}
/**
* ssc_keydown event handler.
* @param {Object} event
*/
function ssc_keydown(event) {
var target = event.target;
var modifier = event.ctrlKey || event.altKey || event.metaKey;
// do nothing if user is editing text
// or using a modifier ssc_key (except shift)
if ( /input|textarea|embed/i.test(target.nodeName) ||
target.isContentEditable ||
event.defaultPrevented ||
modifier ) { return true; }
// spacebar should trigger button press
if (ssc_isNodeName(target, "button") &&
event.keyCode === ssc_key.spacebar) { return true; }
var shift, x = 0, y = 0;
var elem = ssc_overflowingAncestor(ssc_activeElement);
var clientHeight = elem.clientHeight;
if (elem == document.body) {
clientHeight = window.innerHeight;
}
switch (event.keyCode) {
case ssc_key.up:
y = -ssc_arrowscroll;
break;
case ssc_key.down:
y = ssc_arrowscroll;
break;
case ssc_key.spacebar:
// (+ shift)
shift = event.shiftKey ? 1 : -1;
y = -shift * clientHeight * 0.9;
break;
case ssc_key.pageup:
y = -clientHeight * 0.9;
break;
case ssc_key.pagedown:
y = clientHeight * 0.9;
break;
case ssc_key.home:
y = -elem.scrollTop;
break;
case ssc_key.end:
var damt = elem.scrollHeight - elem.scrollTop - clientHeight;
y = (damt > 0) ? damt+10 : 0;
break;
case ssc_key.left:
x = -ssc_arrowscroll;
break;
case ssc_key.right:
x = ssc_arrowscroll;
break;
default:
return true;
// a ssc_key we don't care about
}
ssc_scrollArray(elem, x, y);
event.preventDefault();
}
/**
* ssc_mousedown event only for updating ssc_activeElement
*/
function ssc_mousedown(event) {
ssc_activeElement = event.target;
}
/***********************************************
* OVERFLOW
***********************************************/
var ssc_cache = {}; // cleared out every once in while
setInterval(function(){ ssc_cache = {}; }, 10 * 1000);
var ssc_uniqueID = (function() {
var i = 0;
return function (el) {
return el.ssc_uniqueID || (el.ssc_uniqueID = i++);
};
})();
function ssc_setCache(elems, overflowing) {
for (var i = elems.length; i--;) ssc_cache[ssc_uniqueID(elems[i])] = overflowing;
return overflowing;
}
function ssc_overflowingAncestor(el) {
var elems = [];
var ssc_rootScrollHeight = ssc_root.scrollHeight;
do {
var cached = ssc_cache[ssc_uniqueID(el)];
if (cached) {
return ssc_setCache(elems, cached);
}
elems.push(el);
if (ssc_rootScrollHeight === el.scrollHeight) {
if (!ssc_frame || ssc_root.clientHeight + 10 < ssc_rootScrollHeight) {
return ssc_setCache(elems, document.body); // scrolling ssc_root in WebKit
}
}
else if (el.clientHeight + 10 < el.scrollHeight) {
overflow = getComputedStyle(el, "").getPropertyValue("overflow");
if (overflow === "scroll" || overflow === "auto") {
return ssc_setCache(elems, el);
}
}
} while (el = el.parentNode);
}
/***********************************************
* HELPERS
***********************************************/
function ssc_addEvent(type, fn, bubble) {
window.addEventListener(type, fn, (bubble||false));
}
function ssc_removeEvent(type, fn, bubble) {
window.removeEventListener(type, fn, (bubble||false));
}
function ssc_isNodeName(el, tag) {
return el.nodeName.toLowerCase() === tag.toLowerCase();
}
function ssc_directionCheck(x, y) {
x = (x > 0) ? 1 : -1;
y = (y > 0) ? 1 : -1;
if (ssc_direction.x !== x || ssc_direction.y !== y) {
ssc_direction.x = x;
ssc_direction.y = y;
ssc_que = [];
}
}
/***********************************************
* ssc_pulse
***********************************************/
/**
* Viscous fluid with a ssc_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 ssc_pulse_(x) {
var val, start, expx;
// test
x = x * ssc_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 * ssc_pulseNormalize;
}
function ssc_pulse(x) {
if (x >= 1) return 1;
if (x <= 0) return 0;
if (ssc_pulseNormalize == 1) {
ssc_pulseNormalize /= ssc_pulse_(1);
}
return ssc_pulse_(x);
}
$.browser.chrome = /chrome/.test(navigator.userAgent.toLowerCase());
if ( $.browser.chrome ) {
ssc_addEvent("mousedown", ssc_mousedown);
ssc_addEvent("mousewheel", ssc_wheel);
ssc_addEvent("load", ssc_init);
}
});
function ssc_init() {
if (document.body) {
var e = document.body, s = document.documentElement, c = window.innerHeight, t = e.scrollHeight;
if (ssc_root = document.compatMode.indexOf("CSS") >= 0 ? s : e, ssc_activeElement = e, ssc_initdone = !0, top != self) {
ssc_frame = !0;
}
else if (t > c && (e.offsetHeight <= c || s.offsetHeight <= c) && (ssc_root.style.height = "auto", ssc_root.offsetHeight <= c)) {
var r = document.createElement("div");
r.style.clear = "both", e.appendChild(r)
}
ssc_fixedback || (e.style.backgroundAttachment = "scroll", s.style.backgroundAttachment = "scroll"), ssc_keyboardsupport && ssc_addEvent("keydown", ssc_keydown);
}
}
function ssc_scrollArray(e, s, c, t) {
if (t || (t = 1e3), ssc_directionCheck(s, c), ssc_que.push({x: s,y: c,lastX: 0 > s ? .99 : -.99,lastY: 0 > c ? .99 : -.99,start: +new Date}), !ssc_pending) {
var r = function() {
for (var o = +new Date, n = 0, a = 0, i = 0; i < ssc_que.length; i++) {
var l = ssc_que[i], _ = o - l.start, u = _ >= ssc_animtime, d = u ? 1 : _ / ssc_animtime;
ssc_pulseAlgorithm && (d = ssc_pulse(d));
var f = l.x * d - l.lastX >> 0, m = l.y * d - l.lastY >> 0;
n += f, a += m, l.lastX += f, l.lastY += m, u && (ssc_que.splice(i, 1), i--)
}
if (s) {
var h = e.scrollLeft;
e.scrollLeft += n, n && e.scrollLeft === h && (s = 0)
}
if (c) {
var p = e.scrollTop;
e.scrollTop += a, a && e.scrollTop === p && (c = 0)
}
s || c || (ssc_que = []), ssc_que.length ? setTimeout(r, t / ssc_framerate + 1) : ssc_pending = !1
};
setTimeout(r, 0), ssc_pending = !0;
}
}
function ssc_wheel(e) {
ssc_initdone || ssc_init();
var s = e.target, c = ssc_overflowingAncestor(s);
if (!c || e.defaultPrevented || ssc_isNodeName(ssc_activeElement, "embed") || ssc_isNodeName(s, "embed") && /\.pdf/i.test(s.src)) {
return !0;
}
var t = e.wheelDeltaX || 0, r = e.wheelDeltaY || 0;
t || r || (r = e.wheelDelta || 0), Math.abs(t) > 1.2 && (t *= ssc_stepsize / 120), Math.abs(r) > 1.2 && (r *= ssc_stepsize / 190), ssc_scrollArray(c, -t, -r), e.preventDefault()
}
function ssc_keydown(e) {
var s = e.target, c = e.ctrlKey || e.altKey || e.metaKey;
if (/input|textarea|embed/i.test(s.nodeName) || s.isContentEditable || e.defaultPrevented || c) {
return !0;
}
if (ssc_isNodeName(s, "button") && e.keyCode === ssc_key.spacebar) {
return !0;
}
var t, r = 0, o = 0, n = ssc_overflowingAncestor(ssc_activeElement), a = n.clientHeight;
switch (n == document.body && (a = window.innerHeight), e.keyCode) {
case ssc_key.up:
o = -ssc_arrowscroll;
break;
case ssc_key.down:
o = ssc_arrowscroll;
break;
case ssc_key.spacebar:
t = e.shiftKey ? 1 : -1, o = -t * a * .9;
break;
case ssc_key.pageup:
o = .9 * -a;
break;
case ssc_key.pagedown:
o = .9 * a;
break;
case ssc_key.home:
o = -n.scrollTop;
break;
case ssc_key.end:
var i = n.scrollHeight - n.scrollTop - a;
o = i > 0 ? i + 10 : 0;
break;
case ssc_key.left:
r = -ssc_arrowscroll;
break;
case ssc_key.right:
r = ssc_arrowscroll;
break;
default:
return !0
}
ssc_scrollArray(n, r, o), e.preventDefault();
}
function ssc_mousedown(e) {
ssc_activeElement = e.target;
}
function ssc_setCache(e, s) {
for (var c = e.length; c--; ) {
ssc_cache[ssc_uniqueID(e[c])] = s;
}
return s;
}
function ssc_overflowingAncestor(e) {
var s = [], c = ssc_root.scrollHeight;
do {
var t = ssc_cache[ssc_uniqueID(e)];
if (t) {
return ssc_setCache(s, t);
}
if (s.push(e), c === e.scrollHeight) {
if (!ssc_frame || ssc_root.clientHeight + 10 < c) {
return ssc_setCache(s, document.body);
}
} else if (e.clientHeight + 10 < e.scrollHeight && (overflow = getComputedStyle(e, "").getPropertyValue("overflow"), "scroll" === overflow || "auto" === overflow)) {
return ssc_setCache(s, e);
}
} while (e = e.parentNode);
}
function ssc_addEvent(e, s, c) {
window.addEventListener(e, s, c || !1);
}
function ssc_removeEvent(e, s, c) {
window.removeEventListener(e, s, c || !1);
}
function ssc_isNodeName(e, s) {
return e.nodeName.toLowerCase() === s.toLowerCase();
}
function ssc_directionCheck(e, s) {
e = e > 0 ? 1 : -1, s = s > 0 ? 1 : -1, (ssc_direction.x !== e || ssc_direction.y !== s) && (ssc_direction.x = e, ssc_direction.y = s, ssc_que = []);
}
function ssc_pulse_(e) {
var s, c, t;
return e *= ssc_pulseScale, 1 > e ? s = e - (1 - Math.exp(-e)) : (c = Math.exp(-1), e -= 1, t = 1 - Math.exp(-e), s = c + t * (1 - c)), s * ssc_pulseNormalize;
}
function ssc_pulse(e) {
return e >= 1 ? 1 : 0 >= e ? 0 : (1 == ssc_pulseNormalize && (ssc_pulseNormalize /= ssc_pulse_(1)), ssc_pulse_(e));
}
var ssc_framerate = 150,
ssc_animtime = 500,
ssc_stepsize = 150,
ssc_pulseAlgorithm = !0,
ssc_pulseScale = 6,
ssc_pulseNormalize = 1,
ssc_keyboardsupport = !0,
ssc_arrowscroll = 50,
ssc_frame = !1,
ssc_direction = {x: 0,y: 0},
ssc_initdone = !1,
ssc_fixedback = !0,
ssc_root = document.documentElement,
ssc_activeElement,
ssc_key = {left: 37,up: 38,right: 39,down: 40,spacebar: 32,pageup: 33,pagedown: 34,end: 35,home: 36},
ssc_que = [],
ssc_pending = !1,
ssc_cache = {};
setInterval(function() {
ssc_cache = {}
}, 1e4);
var ssc_uniqueID = function() {
var e = 0;
return function(s) {
return s.ssc_uniqueID || (s.ssc_uniqueID = e++)
}
}();
$.browser.chrome = /chrome/.test(navigator.userAgent.toLowerCase()), $.browser.chrome && (ssc_addEvent("mousedown", ssc_mousedown), ssc_addEvent("mousewheel", ssc_wheel), ssc_addEvent("load", ssc_init));
function ssc_init(){if(document.body){var a=document.body,b=document.documentElement,c=window.innerHeight,d=a.scrollHeight;if(ssc_root=document.compatMode.indexOf("CSS")>=0?b:a,ssc_activeElement=a,ssc_initdone=!0,top!=self)ssc_frame=!0;else if(d>c&&(a.offsetHeight<=c||b.offsetHeight<=c)&&(ssc_root.style.height="auto",ssc_root.offsetHeight<=c)){var e=document.createElement("div");e.style.clear="both",a.appendChild(e)}ssc_fixedback||(a.style.backgroundAttachment="scroll",b.style.backgroundAttachment="scroll"),ssc_keyboardsupport&&ssc_addEvent("keydown",ssc_keydown)}}function ssc_scrollArray(a,b,c,d){if(d||(d=1e3),ssc_directionCheck(b,c),ssc_que.push({x:b,y:c,lastX:0>b?.99:-.99,lastY:0>c?.99:-.99,start:+new Date}),!ssc_pending){var e=function(){for(var f=+new Date,g=0,h=0,i=0;i<ssc_que.length;i++){var j=ssc_que[i],k=f-j.start,l=k>=ssc_animtime,m=l?1:k/ssc_animtime;ssc_pulseAlgorithm&&(m=ssc_pulse(m));var n=j.x*m-j.lastX>>0,o=j.y*m-j.lastY>>0;g+=n,h+=o,j.lastX+=n,j.lastY+=o,l&&(ssc_que.splice(i,1),i--)}if(b){var p=a.scrollLeft;a.scrollLeft+=g,g&&a.scrollLeft===p&&(b=0)}if(c){var q=a.scrollTop;a.scrollTop+=h,h&&a.scrollTop===q&&(c=0)}b||c||(ssc_que=[]),ssc_que.length?setTimeout(e,d/ssc_framerate+1):ssc_pending=!1};setTimeout(e,0),ssc_pending=!0}}function ssc_wheel(a){ssc_initdone||ssc_init();var b=a.target,c=ssc_overflowingAncestor(b);if(!c||a.defaultPrevented||ssc_isNodeName(ssc_activeElement,"embed")||ssc_isNodeName(b,"embed")&&/\.pdf/i.test(b.src))return!0;var d=a.wheelDeltaX||0,e=a.wheelDeltaY||0;d||e||(e=a.wheelDelta||0),Math.abs(d)>1.2&&(d*=ssc_stepsize/120),Math.abs(e)>1.2&&(e*=ssc_stepsize/190),ssc_scrollArray(c,-d,-e),a.preventDefault()}function ssc_keydown(a){var b=a.target,c=a.ctrlKey||a.altKey||a.metaKey;if(/input|textarea|embed/i.test(b.nodeName)||b.isContentEditable||a.defaultPrevented||c)return!0;if(ssc_isNodeName(b,"button")&&a.keyCode===ssc_key.spacebar)return!0;var d,e=0,f=0,g=ssc_overflowingAncestor(ssc_activeElement),h=g.clientHeight;switch(g==document.body&&(h=window.innerHeight),a.keyCode){case ssc_key.up:f=-ssc_arrowscroll;break;case ssc_key.down:f=ssc_arrowscroll;break;case ssc_key.spacebar:d=a.shiftKey?1:-1,f=.9*-d*h;break;case ssc_key.pageup:f=.9*-h;break;case ssc_key.pagedown:f=.9*h;break;case ssc_key.home:f=-g.scrollTop;break;case ssc_key.end:var i=g.scrollHeight-g.scrollTop-h;f=i>0?i+10:0;break;case ssc_key.left:e=-ssc_arrowscroll;break;case ssc_key.right:e=ssc_arrowscroll;break;default:return!0}ssc_scrollArray(g,e,f),a.preventDefault()}function ssc_mousedown(a){ssc_activeElement=a.target}function ssc_setCache(a,b){for(var c=a.length;c--;)ssc_cache[ssc_uniqueID(a[c])]=b;return b}function ssc_overflowingAncestor(a){var b=[],c=ssc_root.scrollHeight;do{var d=ssc_cache[ssc_uniqueID(a)];if(d)return ssc_setCache(b,d);if(b.push(a),c===a.scrollHeight){if(!ssc_frame||ssc_root.clientHeight+10<c)return ssc_setCache(b,document.body)}else if(a.clientHeight+10<a.scrollHeight&&(overflow=getComputedStyle(a,"").getPropertyValue("overflow"),"scroll"===overflow||"auto"===overflow))return ssc_setCache(b,a)}while(a=a.parentNode)}function ssc_addEvent(a,b,c){window.addEventListener(a,b,c||!1)}function ssc_removeEvent(a,b,c){window.removeEventListener(a,b,c||!1)}function ssc_isNodeName(a,b){return a.nodeName.toLowerCase()===b.toLowerCase()}function ssc_directionCheck(a,b){a=a>0?1:-1,b=b>0?1:-1,(ssc_direction.x!==a||ssc_direction.y!==b)&&(ssc_direction.x=a,ssc_direction.y=b,ssc_que=[])}function ssc_pulse_(a){var b,c,d;return a*=ssc_pulseScale,1>a?b=a-(1-Math.exp(-a)):(c=Math.exp(-1),a-=1,d=1-Math.exp(-a),b=c+d*(1-c)),b*ssc_pulseNormalize}function ssc_pulse(a){return a>=1?1:0>=a?0:(1==ssc_pulseNormalize&&(ssc_pulseNormalize/=ssc_pulse_(1)),ssc_pulse_(a))}var ssc_framerate=150,ssc_animtime=500,ssc_stepsize=150,ssc_pulseAlgorithm=!0,ssc_pulseScale=6,ssc_pulseNormalize=1,ssc_keyboardsupport=!0,ssc_arrowscroll=50,ssc_frame=!1,ssc_direction={x:0,y:0},ssc_initdone=!1,ssc_fixedback=!0,ssc_root=document.documentElement,ssc_activeElement,ssc_key={left:37,up:38,right:39,down:40,spacebar:32,pageup:33,pagedown:34,end:35,home:36},ssc_que=[],ssc_pending=!1,ssc_cache={};setInterval(function(){ssc_cache={}},1e4);var ssc_uniqueID=function(){var a=0;return function(b){return b.ssc_uniqueID||(b.ssc_uniqueID=a++)}}();$.browser.chrome=/chrome/.test(navigator.userAgent.toLowerCase()),$.browser.chrome&&(ssc_addEvent("mousedown",ssc_mousedown),ssc_addEvent("mousewheel",ssc_wheel),ssc_addEvent("load",ssc_init));