Smooth Scroll

By Shinya Last update Jun 2, 2008 — Installed 815 times. Daily Installs: 3, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 1, 0, 0, 3, 0, 0, 1, 0, 0, 0, 0, 2, 2, 0, 2, 0, 2, 3
// ==UserScript==
// @name           Smooth Scroll
// @description    Smooths scroll for links within the page.
// @version        2.1.0
// @author         Shinya
// @namespace      http://www.code-404.net/
// @homepage       http://userscripts.org/scripts/show/12581
// @include        *
// @note           Based on: LDRize.
// ==/UserScript==

// =============================================
// This script is based on LDRize.
// http://userscripts.org/scripts/show/11562
// 
// Thanks snj14.
// =============================================

(function(){
	function SmoothScroll(aNode){
		this.node = aNode;
		this._destX = null;
		this._destY = null;
		this._id_list = [];
	}
	
	SmoothScroll.USE_HISTORY = true;
	SmoothScroll.STEPS = 200;
	SmoothScroll.DURATION = 6000;
	
	SmoothScroll.prototype = {
		getPosition: function(){
			this.stop();
			
			// for nested HTML elements
			while(this.node.tagName.toLowerCase() != "a"){
				this.node = this.node.parentNode;
			}
			
			// get target node
			var fragment = this.node.hash.substr(1, this.node.hash.length - 1);
			var node = document.getElementById(fragment) || (function(){
				var anchors = document.getElementsByTagName("a");
				for(var i = 0, l = anchors.length; i < l; i++){
					if(
					  anchors[i].hasAttribute("name") &&
					  anchors[i].getAttribute("name") == fragment
					){
						return anchors[i];
					}
				}
			})();
			if(node == null) return;
			
			// get target position
			var x = node.offsetLeft, y = node.offsetTop;
			while(node = node.offsetParent){
				x += node.offsetLeft;
				y += node.offsetTop;
			}
			if(x > window.scrollMaxX) x = window.scrollMaxX;
			if(y > window.scrollMaxY) y = window.scrollMaxY;
			
			// scroll!
			this.scrollTo(x, y);
			
			if(SmoothScroll.USE_HISTORY){
				setTimeout(function(){
					document.location.hash = "#" + fragment;
				}, (SmoothScroll.DURATION / SmoothScroll.STEPS) - 1);
			}
		},
		
		scrollTo: function(destX, destY){
			this._destX = destX;
			this._destY = destY;
			
			var y = window.pageYOffset, x = window.pageXOffset, time;
			for(var i = 1, l = SmoothScroll.STEPS; i < l; i++){
				x = destX - ((destX - x) / 2);
				y = destY - ((destY - y) / 2);
				time = (SmoothScroll.DURATION / SmoothScroll.STEPS) * i;
				if((Math.abs(destY - y) < 1 && Math.abs(destX - x) < 1) || i + 1 == SmoothScroll.STEPS){
					var id = setTimeout(this.makeScrollTo(destX, destY), time);
					var id2 = setTimeout(this.resetDestination, time);
					this._id_list.push(id);
					this._id_list.push(id2);
					break;
				} else {
					var id = setTimeout(this.makeScrollTo(x, y), time);
					this._id_list.push(id);
				}
			}
		},
		
		makeScrollTo: function(x, y){
			return function(){
				window.scrollTo(x, y);
			}
		},
		
		resetDestination: function(){
			this._destX = null;
			this._destY = null;
		},
		
		stop: function(){
			if(this._id_list.length){
				this.clearTimer();
				if(this._destX || this._destY){
					this.makeScrollTo(this._destX, this._destY).call();
				}
			}
			this.resetDestination();
		},
		
		clearTimer: function(){
			this._id_list.forEach(function(id){
				clearTimeout(id);
			});
			this._id_list = [];
			
		},
	}
	
	if(document.body){
		var anchors = document.getElementsByTagName("a");
		for(var i = 0, l = anchors.length; i < l; i++){
			if(
			  anchors[i].hasAttribute("href") &&          // web link
			  !anchors[i].hasAttribute("onclick") &&      // not have event
			  anchors[i].href.match(/#/) &&               // have fragment
			  (anchors[i].getAttribute("href") != "#") && // not like have event
			  // same page
			  anchors[i].href.replace(/#.*$/, "") == document.location.href.replace(/#.*$/, "")
			){
				anchors[i].addEventListener("click", function(aEvent){
					var scroller = new SmoothScroll(aEvent.target);
					scroller.getPosition();
					aEvent.preventDefault();
				}, false);
			}
		}
	}
})();