Smoothscroll

Smooth scrolling on pages using javascript and jquery

Stan na 28-05-2016. Zobacz najnowsza wersja.

// ==UserScript==
// @name Smoothscroll
// @include     http*
// @author       Creec Winceptor
// @description  Smooth scrolling on pages using javascript and jquery
// @namespace https://greasyfork.org/users/3167
// @run-at document-idle
// @grant    none
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_registerMenuCommand
// @version 0.0.1.20160528163032
// ==/UserScript==







//DEFAULT SETTINGS HERE 
//DO NOT CHANGE ANYTHING HERE ANYMORE, USE SCRIPT COMMANDS -> CONFIGURE SMOOTHSCROLL






//Smoothness factor value (how strong the smoothing effect is)
//values: 1-(infinite) (default = 30)
var smoothness = 30;


//Scroll sensitivity
//values: anything? (default 1.00)
var sensitivity = 1.00;


//Acceleration sensitivity
//values: anything? (default 1.00)
var acceleration = 1.00;


//Refreshrate setting
//values: 30-144 (default = 60/72/120/144 = same as your monitor hz)
var baserefreshrate = 60;


//Alternative scrolling multiplier
//values: true/false (try to set this to true if scrolling is too slow/doesn't work)
var alternative_sesitivity_multiplier = false;







//CODE STARTS HERE

var minimal_jquery_version = 200;

var DEBUG = false;

var WEBKIT = false;

//this.$ = this.jQuery = jQuery.noConflict(true);

if (window.top != window.self)  //don't run on frames or iframes
    return;



if (smoothness>100)
{
	smoothness = 100;
}

if (baserefreshrate <= 30 || baserefreshrate>144)
{
	baserefreshrate = 144;
}
refreshrate = baserefreshrate;

var animationduration = Math.round(1000/refreshrate);
//var relativeratio = Math.round(51-smoothness/2)/100;
var relativeratio = Math.round(1/(1+smoothness)*100)/100;
//var relativeratio = relativeratio;



var lastLoop = new Date;
//var lastrefreshrate = 0;
function gameLoop() { 
	var thisLoop = new Date;
    var refreshrate0 = 1000 / (thisLoop - lastLoop + 1);
    lastLoop = thisLoop;
	
	refreshrate = refreshrate + (refreshrate0-refreshrate)*0.01;
	refreshrate = Math.round(refreshrate);
	
	if (DEBUG)
	{
		console.log(refreshrate);
	}
	
	animationduration = Math.round(1000/(refreshrate));
	//var relativeratio = Math.round(51-smoothness/2)/100;
	relativeratio = Math.round(1/(1+smoothness*refreshrate/baserefreshrate)*100)/100;
}
gameLoop();


function InitSmoothscroll()
{
  
  //LoadConfig();

  InitConfigmenu();

var startposition = false;
var targetposition = 0;
var position = 0;

var scrollfocus = ss$('body');
function hasScrollBarVisible(element)
{
  //return (document.documentElement.scrollHeight !== document.documentElement.clientHeight);
 
  // Get the computed style of the body element
  var cStyle = element.currentStyle||window.getComputedStyle(element, "");
 
  // Check the overflow and overflowY properties for "auto" and "visible" values
  var scrollbar1 = cStyle.overflow == "scroll" || cStyle.overflowY == "scroll";

	var scrollbar2 = cStyle.overflow == "auto" || cStyle.overflowY == "auto";
	
	var scrollbar = scrollbar1 || scrollbar2;
 
  return scrollbar;
}


function hasscrollbars(scrollfocus)
{
	var hasvisiblescrollbars = hasScrollBarVisible(scrollfocus);
	var parentelement = ss$(scrollfocus).parent();
	if ( ss$(parentelement))
	{
		//if (ss$(parentelement).is("textarea") || ss$(scrollfocus).is("textarea"))
		if (ss$(parentelement).is("textarea") || ss$(scrollfocus).is("textarea") || ss$(parentelement).is("article") || ss$(parentelement).is("article"))
		{
			return true;
		}
		else
		{
			if (ss$(parentelement).hasClass( "yt-scrollbar" ) || ss$(scrollfocus).hasClass( "yt-scrollbar" ))
			{
				return true;
			}
			return hasvisiblescrollbars;
		}
	}
	else
	{
		//scrollfocus = ss$('body');
		//maxposition = ss$(scrollfocus).height();
		return hasvisiblescrollbars;
	}
	return false;
}

function UpdatePosition(element)
{
	gameLoop();
	
	var positiondelta = ss$(element)[0].getAttribute( "positiondelta" );
	//var smoothdelta = Math.sqrt(positiondelta*positiondelta*relativeratio);
	
	var smoothdelta = Math.sqrt(positiondelta*positiondelta*relativeratio);
	
	if (positiondelta<0)
	{
		smoothdelta = smoothdelta*(-1);
	}
	
	//var relative = position - ss$(element).scrollTop();
	//console.log("smoothdelta:" + smoothdelta);
	
	if (Math.abs( (positiondelta-smoothdelta)) <= 1 )
	{
		ss$(element).stop();
		ss$(element).animate({
			scrollTop: '+=' + Math.round(positiondelta)
		}, animationduration, "linear", function() {
			ss$(element).attr( "positiondelta",0 );
			ss$(element)[0].setAttribute( "positiondelta",0 );
			if (DEBUG)
			{
				ss$(element).css( "border", "1px solid red" );
			}
		});
	}
	else
	{
		
		ss$(element).stop();
		ss$(element).animate({
			scrollTop: '+=' + Math.round(smoothdelta)
		}, animationduration, "linear", function() {
			ss$(element).attr( "positiondelta",positiondelta-smoothdelta );
			ss$(element)[0].setAttribute("positiondelta",positiondelta-smoothdelta );
			UpdatePosition(element);
			if (DEBUG)
			{
				ss$(element).css( "border", "1px solid red" );
			}
		});
	}
}


 function MouseScroll (x,y,e) {
	 scrollfocus = UpdateFocus(x,y);
	
	 var positiondelta = 0;
	 var lastscrolltop = 0;

	 var parentelement = ss$(scrollfocus).parent();
	 if ( ss$(parentelement))
	 {
		 if (ss$(parentelement).is("textarea") || ss$(scrollfocus).is("textarea"))
		 {
			 //return true;
		 }
		 else
		 {
			 if ( ss$(parentelement).height() < ss$(scrollfocus).height())
			 {
				 //maxposition = ss$(scrollfocus).height() - ss$(parentelement).height();
			 }
			 else
			 {
				 if (ss$(scrollfocus).height()==0)
				 {
					 //scrollfocus = ss$('body');
					 //maxposition = ss$(scrollfocus).height();
				 }

				 //scrollfocus = ss$('body');
				 //return MouseScroll (event);
				 //return true;
			 }
		 }
	 }
	 else
	 {
		 //scrollfocus = ss$('html');
		 //maxposition = ss$(scrollfocus).height();
	 }
	 
	 var rolled = y;
	 
	 //console.log("rolled: " + rolled);
	
	 //if (ss$(scrollfocus).data("positiondelta" )==undefined)
	 //$embellishment.data("embellishmentid",1)
	 
	 	var lastscrolltop = ss$(scrollfocus).scrollTop();
	 	ss$(scrollfocus).scrollTop(lastscrolltop+rolled);
	 	if (ss$(scrollfocus).scrollTop()==lastscrolltop)
		{
			if (!ss$(scrollfocus).is("html"))
			{
				focus = parentelement;
				//focus = UpdateFocus(event);
				//return MouseScroll (event);
				//console.log("false");
				//return false;
			}
			else
			{
              	//console.log("true");
				//return false;
			}
		}
	 	else
		{
			e.preventDefault();
		}
	    ss$(scrollfocus).scrollTop(lastscrolltop);
   
 	//console.log(scrollfocus);
   
	 	 if (ss$(scrollfocus)[0].getAttribute("positiondelta")==undefined)
		 
		 {
			 positiondelta = 0;
			 //console.log("positiondelta: undefined");
		 }
	 else
		 {
			 positiondelta = ss$(scrollfocus)[0].getAttribute("positiondelta");
			 //console.log("positiondelta: " + positiondelta);
		 }
	 positiondelta = positiondelta*1;
   
	 var direction = rolled/Math.abs(rolled);
	 //var positiondeltadelta = rolled*sensitivity + Math.sqrt(Math.abs(positiondelta/rolled))*acceleration*rolled;
	 
	 //var positiondeltadelta = rolled*(sensitivity+Math.sqrt(Math.abs(positiondelta))*acceleration);
	 //
	 var positiondeltadelta = Math.round(rolled*sensitivity + Math.sqrt(Math.abs(positiondelta-rolled*sensitivity))*acceleration*rolled*0.03/sensitivity);
	 
	 positiondelta = positiondelta + positiondeltadelta;
	
	 ss$(scrollfocus)[0].setAttribute("positiondelta",positiondelta );
	 
	 
	 UpdatePosition(ss$(scrollfocus));
	 
	 //element.innerHTML = "";
	 //console.log("pos:" + position);
	 //event.preventDefault();
	 return true;
 }

 function canscroll(element, dir)
{
	var scrollable = ss$(element);
	var lastscrolltop = ss$(scrollable).scrollTop();
	ss$(scrollable).scrollTop(lastscrolltop+dir);
	if (ss$(scrollable).scrollTop()==lastscrolltop)
	{
		ss$(scrollable).scrollTop(lastscrolltop);
		return false;
	}
	else
	{
		ss$(scrollable).scrollTop(lastscrolltop);
		return true;
	}
	
}

function UpdateFocus(x,y) {
	 /*var dir = 0;
	 if ('wheelDelta' in event) {
		 dir = event.wheelDelta;
	 }
	 else {  // Firefox
		 // The measurement units of the detail and wheelDelta properties are different.
		 dir = event.detail*(-120);
	 }*/
	var dir = y;
	//console.log(dir);
	 //dir = dir*(-1);
	 //
	 	var nodelist = document.querySelectorAll( ":hover" );
		ss$(nodelist).stop();
  	//console.log(nodelist);
	 	if (WEBKIT)
        {
        	scrollfocus = ss$('body');
        }
  		else
        {
            scrollfocus = ss$('html');
        }
  		
	 	for (var i = nodelist.length-1; i >= 0; i--) { 
				//var parent = nodelist[i-1];
			  var newfocus = nodelist[i];
				if (DEBUG)
				{
					ss$(newfocus).css( "border", "1px solid blue" );
					//var debugtimer = setTimeout(function(){ ss$(newfocus).css( "border", "0px solid white" ); }, 1000);
				}
				//if (ss$(newfocus).hasScrollBar3() && hasScrollBarVisible(newfocus) && canscroll(newfocus, dir) && hasscrollbars(newfocus))
				if (canscroll(newfocus, dir) && hasscrollbars(newfocus))
				{
					scrollfocus = ss$(newfocus);
					return newfocus;
				}
 		}

	 return scrollfocus;
 }
	
		ss$('body').bind({
			/*
			 mousewheel: function(e) {
			if (DEBUG)
			{
				console.log(scrollfocus);
			}
			  console.log("scrolling");
			  MouseScroll(e.originalEvent);
		  },
		  */
			
		  mousedown: function(e) {
			 if (DEBUG)
			{
				console.log(scrollfocus);
			}
			  if (scrollfocus)
			{
			  ss$(scrollfocus)[0].setAttribute( "positiondelta",0 );
			  ss$(scrollfocus).stop();
			}
				//scrollfocus = UpdateFocus(e.originalEvent);
			  //console.log("click");
		  }
		});
//Init(window);

(function(window,document) {

    var prefix = "", _addEventListener, onwheel, support;

    // detect event model
    if ( window.addEventListener ) {
        _addEventListener = "addEventListener";
    } else {
        _addEventListener = "attachEvent";
        prefix = "on";
    }

    // detect available wheel event
    support = "onwheel" in document.createElement("div") ? "wheel" : // Modern browsers support "wheel"
              document.onmousewheel !== undefined ? "mousewheel" : // Webkit and IE support at least "mousewheel"
              "DOMMouseScroll"; // let's assume that remaining browsers are older Firefox

    window.addWheelListener = function( elem, callback, useCapture ) {
        _addWheelListener( elem, support, callback, useCapture );

        // handle MozMousePixelScroll in older Firefox
        if( support == "DOMMouseScroll" ) {
            _addWheelListener( elem, "MozMousePixelScroll", callback, useCapture );
        }
    };

    function _addWheelListener( elem, eventName, callback, useCapture ) {
        elem[ _addEventListener ]( prefix + eventName, support == "wheel" ? callback : function( originalEvent ) {
            !originalEvent && ( originalEvent = window.event );

            // create a normalized event object
            var event = {
                // keep a ref to the original event object
                originalEvent: originalEvent,
                target: originalEvent.target || originalEvent.srcElement,
                type: "wheel",
                deltaMode: originalEvent.type == "MozMousePixelScroll" ? 0 : 1,
                deltaX: 0,
                deltaZ: 0,
                preventDefault: function() {
                    originalEvent.preventDefault ?
                        originalEvent.preventDefault() :
                        originalEvent.returnValue = false;
                }
            };
            
            // calculate deltaY (and deltaX) according to the event
            if ( support == "mousewheel" ) {
                event.deltaY = - 1/40 * originalEvent.wheelDelta;
                // Webkit also support wheelDeltaX
                originalEvent.wheelDeltaX && ( event.deltaX = - 1/40 * originalEvent.wheelDeltaX );
            } else {
                event.deltaY = originalEvent.detail;
            }

            // it's time to fire the callback
            return callback( event );

        }, useCapture || false );
    }

})(window,document);
	
addWheelListener( window, function( e ) { 
	var mul = 1;
	if (!WEBKIT || alternative_sesitivity_multiplier)
		mul = 40;
	//console.log( e.deltaY ); 
	MouseScroll(e.deltaX*mul,e.deltaY*mul, e);
});	

  /*var oldpos = ss$('body').scrollTop();
  ss$('body').scrollTop(1);
  var iswebkit = ss$('body').scrollTop()>0;
  ss$('body').scrollTop(oldpos);*/

  //WEBKIT = 'webkitRequestAnimationFrame' in window;
  WEBKIT = document.compatMode == 'CSS1Compat'
  
  //console.log("window: " + window);
  console.log("Smoothscroll initiated! Webkit: " + WEBKIT);
}

var JQUERY = false;
var OWNJQUERY = false;
var OLDJQUERY = false;

var old$;
var oldjQuery;

//max retries
var r_max = 10;
var r_count = 0;

function Init(LEGACYWORKAROUND) {
  //if (typeof jQuery == 'function')  {  
  //if (typeof jQuery == 'undefined' || typeof $ == 'undefined' ) 
  JQUERY = true;
  if (typeof $ == 'undefined') 
  {
    JQUERY = false;
  }
  else
  {
    if (typeof old$ == 'undefined') {
      old$ = $;
    }
  }
  
  if (typeof jQuery == 'undefined' ) 
  {
    JQUERY = false;
  } 
  else
  {
    if (typeof oldjQuery == 'undefined') {
      oldjQuery = jQuery;
    }
  }
  
  OWNJQUERY = true;
  if (typeof ss$ == 'undefined' ) 
  {
    OWNJQUERY = false;
  }
  
  if (!OWNJQUERY){
    if (JQUERY) {
      var versiontable = $.fn.jquery.split('.');
      var version = 0;
      for (var i = versiontable.length-1; i >= 0; i--) { 
        var power = versiontable.length-i;
        version += Math.pow(10,power-1)*versiontable[i];
      }

      if (version<=minimal_jquery_version && LEGACYWORKAROUND)
      {
        console.log("Page jQuery OLD! Version: " + version);
        OLDJQUERY = true;
      }
      else
      {
        ss$ = $;
        OWNJQUERY = true;
      }
      
    }
    else
    {
      console.log("Page jQuery Missing!");
    }
  }
  if (!OWNJQUERY && LEGACYWORKAROUND)
  {
    console.log("Loading new jQuery...");  

    //this.$ = this.jQuery = jQuery.noConflict(true);
    //var $ = jQuery.noConflict();

    //this.$ = this.jQuery = jQuery.noConflict(true);


    var filename = "https://code.jquery.com/jquery-2.2.3.js";
    var fileref = document.createElement('script');
    fileref.setAttribute("type","text/javascript");
    fileref.setAttribute("src", filename);

    if (typeof fileref!="undefined")
    {
      var scriptparent = document.getElementsByTagName("head")[0];

      if (typeof scriptparent=="undefined")
      {
        var scriptparent = document.createElement('head');
        document.getElementsByTagName("html")[0].appendChild(scriptparent);
      }
      scriptparent.appendChild(fileref);
    }

    if (r_count<r_max)
    {
      setTimeout(function() {
          Init(LEGACYWORKAROUND);
      }, 300);
      return 0;
    }
    else
    {
      console.log("Failed to load smoothscroll!");
    }
    r_count++;
  }
  if (OWNJQUERY) {
    var versiontable = ss$.fn.jquery.split('.');
    var version = 0;
    for (var i = versiontable.length-1; i >= 0; i--) { 
      var power = versiontable.length-i;
      version += Math.pow(10,power-1)*versiontable[i];
    }

    console.log("Script jQuery OK! Version: " + version);
    InitSmoothscroll();	
  }
  if (LEGACYWORKAROUND) {
    console.log("Restoring original jQuery...");  
    
    if (typeof old$ != 'undefined') {
      $ = old$;
    }
     if (typeof oldjQuery != 'undefined') {
      jQuery = oldjQuery;
    }
    
    if (OLDJQUERY)
    {
  		var versiontable = $.fn.jquery.split('.');
        var version = 0;
        for (var i = versiontable.length-1; i >= 0; i--) { 
          var power = versiontable.length-i;
          version += Math.pow(10,power-1)*versiontable[i];
        }

        console.log("Page jQuery OK! Version: " + version);
    }
    
  }

  console.log("Finished loading smoothscroll!");
}

function LoadConfig()
{
  smoothness = GM_getValue( 'smoothness', smoothness );
  sensitivity = GM_getValue( 'sensitivity', sensitivity );
  acceleration = GM_getValue( 'acceleration', acceleration );
  baserefreshrate = GM_getValue( 'baserefreshrate', baserefreshrate );
  //alternative_sesitivity_multiplier = GM_getValue( 'alternative_sesitivity_multiplier', alternative_sesitivity_multiplier );
  console.log("Config for smoothscroll loaded!")
}

function SaveConfig()
{
  GM_setValue( 'smoothness', document.getElementById('ss-smoothness').value)
  GM_setValue( 'sensitivity', document.getElementById('ss-sensitivity').value)
  GM_setValue( 'acceleration', document.getElementById('ss-acceleration').value)
  GM_setValue( 'baserefreshrate', document.getElementById('ss-baserefreshrate').value)
  //console.log(document.getElementById('ss-alternative_sesitivity_multiplier').checked)
  console.log("Config for smoothscroll saved!")
}

function InitConfigmenu()
{
    var configbar = document.createElement('div');
    configbar.setAttribute("id","ss-configbar");
 	
  	configbar.innerHTML = '<div style="display:block; width: 100%; height: auto; border: 0px solid #aaaaaa; background-color: grey;">Config page for smoothscroll:</div><div id="ss-config" style="margin:3px; display:block; width: auto; height: auto; border: 0px solid #554433;"><table style="width:auto;"><tr><td>Smoothness</td><td><input type="number" id="ss-smoothness" min="0" max="100" value="' + smoothness + '"></td><td> Smoothness factor value (how strong the smoothing effect is)</td></tr><tr><td>Sensitivity</td><td><input type="number" id="ss-sensitivity" min="0" max="100" value="' + sensitivity + '"></td><td> Scroll sensitivity (duh)</td></tr><tr><td>Acceleration</td><td><input type="number" id="ss-acceleration" min="0" max="100" value="' + acceleration + '"></td><td> Acceleration of continuous scroll action (saves your finger)</td></tr><tr><td>Refreshrate</td><td><input type="number" id="ss-baserefreshrate" min="1" max="100" value="' + baserefreshrate + '"></td><td>Refreshrate of scrollanimation (60Hz default)</td></tr></table></div><div style="width: 100%; height: auto; text-align: center; background-color: grey;"><input id="ss-save" type="button" value="Save config" style="width: 44%; height: auto; border: 0px solid #aaaaaa; margin: 3px"/><input id="ss-close" type="button" value="Close config" style="width: 44%; height: auto; border: 0px solid #aaaaaa; margin: 3px"/><div>';
  	
  	
    var configparent = document.getElementsByTagName("body")[0];
  	configbar.style.width = '100%';  
  	configbar.style.display = 'none';  
  	configbar.style.position = 'absolute';
	configbar.style.zIndex = '99';
  	configbar.style.backgroundColor = 'white';
  
  	configparent.insertBefore(configbar, configparent.childNodes[0]);
  	
    ss$("#ss-close").click(function() {
      //ss$("#ss-config").animate({
      //  height: '0'
      //}, 100);
      ss$("#ss-configbar").hide("slow");
    });
     ss$("#ss-save").click(function() {
      SaveConfig();
    });
  
  //ss$("#ss-configbar").hide();
}

function ConfigSmoothscroll()
{
  if (!OWNJQUERY){
    alert("Smoothscroll is not running properly on this page!");
    return;
  }
  //ss$("#ss-config").animate({
  //  height: '100px'
  //}, 100);
  ss$("#ss-configbar").show("slow");
}

GM_registerMenuCommand("Configurate smoothscroll", ConfigSmoothscroll);

console.log("Loading smoothscroll...");
Init(true);
//InitSmoothscroll();