Smoothscroll

Smooth scrolling on pages using javascript

Per 08-12-2017. Zie de nieuwste versie.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

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

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

//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 1.00)
var smoothness = 1;

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

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

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


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


//CODE STARTS HERE

var DEBUG = false;

var WEBKIT = false;

//console.log("Loading smoothscroll...");

var baserefreshrate = 60; //DO NOT CHANGE THIS EVER
if (smoothness>10)
{
	smoothness = 10;
}

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

function InitSmoothscroll()
{
  
  //LoadConfig();

  //InitConfigmenu();

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

//var scrollfocus = //ss$('body');
var scrollfocus = document.body;
var mousemoved = true;
  
//var lastLoop = new Date;  
var nextLoop = 1;
//var nextprint = lastLoop;
var dynamicframedelay = 0;
var dynamicrefreshrate = refreshrate;
function fpscalcloop(animationduration) { 
	var thisLoopDate = new Date;
  	var thisLoop = thisLoopDate.getTime();
    //var fpscalc = 1000 / (thisLoop - lastLoop + 1);
    //lastLoop = thisLoop;
    //
    var delay = thisLoop - nextLoop;
  	//var fpscalc = 1000 / (thisLoop - nextLoop + 1);
  

	
	//refreshrate = refreshrate + (refreshrate0-refreshrate)*0.1;
	//refreshrate = Math.round(refreshrate);
	
	//animationduration = Math.round(1000/(refreshrate*3));
	//console.log(animationduration);
	//var relativeratio = Math.round(51-smoothness/2)/100;
	//relativeratio = Math.round(1/(1+smoothness*baserefreshrate/refreshrate)*100)/100;
	//
	//
	if (animationduration>=0)
    {
       nextLoop = thisLoop+animationduration;
  	
      framedelay = delay*refreshrate/1000;

      if (framedelay>1)
      {
           dynamicframedelay = dynamicframedelay + Math.ceil(framedelay);
      }
      else
      {
        if ( dynamicframedelay>1 && framedelay<1 )
        {
           dynamicframedelay = dynamicframedelay - Math.ceil(dynamicframedelay/2);

        }
      }

      refreshrate = refreshrate*1;
  	 dynamicrefreshrate = Math.round( (refreshrate*refreshrate/(refreshrate+dynamicframedelay))*100 )/100;
    }
  	else
    {
        nextLoop = thisLoop;
    }
	
  
  //console.log("dynamicframedelay: " + dynamicframedelay);
  
  //console.log("dynamicrefreshrate: " + dynamicrefreshrate);
}

  
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);
	return hasvisiblescrollbars;
	
	/*
	var parentelement = //ss$(scrollfocus).parent();
	if ( //ss$(parentelement))
	{
		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
	{
		return hasvisiblescrollbars;
	}
	return false;
	*/
}

var scroll_timeout = null;
  
function direction(number)
{
  if (number>0)
  {
    return 1;
  }
  if (number<0)
  {
    return -1;
  }
  return 0;
}

function UpdatePosition(element)
{
	//fpscalcloop(0);

    var basespeed = 2;

    var animationduration = Math.floor(1000/(dynamicrefreshrate));

    //var relativeratio = Math.ceil( interpolation*( Math.pow(0.003*interpolation,smoothness)/dynamicrefreshrate *baserefreshrate  )*1000000)/1000000;
    //
    //var relativeratio = Math.ceil( ( 0.5/dynamicrefreshrate *baserefreshrate  )*1000000)/1000000;

    var relativeratio = 1-Math.pow( dynamicrefreshrate, -1/dynamicrefreshrate*smoothness*basespeed);

    var positiondelta = scrollpositiondelta(element);

    var dir = direction(positiondelta);

    //console.log("positiondelta:" + positiondelta);

    //var smoothdelta = Math.sqrt(positiondelta*positiondelta*relativeratio);
    //
    var smoothdelta = Math.abs(positiondelta*relativeratio);

    var rounddelta = Math.ceil(smoothdelta);

    //var smoothdelta = positiondelta*relativeratio;

	if ( rounddelta > 1 )
	{	
      	element.scrollTop = element.scrollTop + rounddelta*dir;
			scrollpositiondelta(element, positiondelta - rounddelta*dir);
			
		  scroll_timeout = setTimeout(function() {
      	
			UpdatePosition(element);
		  }, animationduration);
      fpscalcloop(animationduration);
	}
  	else
	{	
		
      if ( positiondelta*dir > 1 )
      {
        
        element.scrollTop = element.scrollTop + dir;
			scrollpositiondelta(element, positiondelta - dir);
			
      	scroll_timeout = setTimeout(function() {
      		
			UpdatePosition(element);
		  }, (animationduration/smoothdelta));
      }
      else
      {
        element.scrollTop = element.scrollTop + rounddelta*dir;
              scrollpositiondelta(element, 0);
          //scroll_timeout = setTimeout(function() {
        	
              
            //}, (animationduration/smoothdelta));
      }
      fpscalcloop( (animationduration/smoothdelta) );
	}
}


 function MouseScroll (e) {

   	var mul = 1;
	if (!WEBKIT || alternative_sensitivity_multiplier)
		mul = 40;
   	
     var x = e.deltaX*mul;
   	var y = e.deltaY*mul;
   
    scrollfocus = UpdateFocus(e);
   
   	/*if (mousemoved)
    {
      scrollfocus = UpdateFocus(x,y);
      mousemoved = false;
    }*/
   
	 var positiondelta = 0;
	 var lastscrolltop = 0;

	 var rolled = y;
	 	
	 	if (!canscroll(scrollfocus, rolled))
		{
			if (scrollfocus != document.documentElement)	
			{
				//scrollfocus = UpdateFocus(e);
              return false;
			}
			else
			{
              	
              	//console.log("true");
				return false;
			}
		}
	 	else
		{
          	if (e.defaultPrevented)
            {
              return true;
            }
          	else
            {
              e.preventDefault();
            }
			
		}
   	 positiondelta = scrollpositiondelta(scrollfocus);
   
	 var direction = rolled/Math.abs(rolled);
	 
	 //var positiondeltadelta = Math.round(rolled*sensitivity + Math.sqrt(Math.abs(positiondelta-rolled*sensitivity))*acceleration*rolled*0.2);
	 //var positiondeltadelta = Math.round(Math.sqrt(Math.abs(positiondelta-rolled*sensitivity))*acceleration*rolled*0.2);
	 
	 var positiondeltadelta = rolled*sensitivity;
   var positiondeltacceleration = Math.sqrt(Math.abs(positiondelta/positiondeltadelta*acceleration));
   //console.log("positiondelta: "+positiondelta+" positiondeltadelta: " + positiondeltadelta + " positiondeltacceleration: " + positiondeltacceleration);
	 positiondelta = Math.round(positiondelta*1 + positiondeltadelta*(1+positiondeltacceleration) );
	 
  	 scrollpositiondelta(scrollfocus, positiondelta)

	 clearTimeout(scroll_timeout);
   
   		fpscalcloop(-1);
	 
	 UpdatePosition(scrollfocus);
   
   		//console.log(e);
	 
	 return true;
 }

 function canscroll(element, dir)
{
  	if (dir>0)
    {
      dir = 1;
    }
  	if (dir<0)
    {
      dir = -1;
    }
  	var checkradius = 3; //pixels to try scrolling
  
  	var canscroll0 = false;

	var scrollable = element;

	var lastscrolltop = scrollable.scrollTop;

	scrollable.scrollTop = lastscrolltop+dir*checkradius;

	if (scrollable.scrollTop!=lastscrolltop)
	{
		canscroll0 = true;
	}

	scrollable.scrollTop = lastscrolltop;

	return canscroll0;
}
  
  
function scrollpositiondelta(element, newdelta)
{
	var target = element;
  	var delta = 0;
  	if (newdelta!=undefined)
    {
    target.positiondelta = newdelta;
      delta = newdelta;
    }
	else
    {
		var olddelta = target.positiondelta;
      if (olddelta!=undefined)
      {
      	delta = olddelta;
      }
    }
  	return delta*1;
}

function UpdateFocus(e) {
    var x = e.deltaX;
   	var y = e.deltaY;
	if (scrollfocus)
      {
        //scrollpositiondelta(scrollfocus, 0);
      }
	var dir = y;
	 	var nodelist = document.querySelectorAll( ":hover" );
	 	if (WEBKIT)
        {
        	//scrollfocus = //ss$('body');
			scrollfocus = document.body;
        }
  		else
        {
            //scrollfocus = //ss$('html');
			scrollfocus = document.documentElement;
        }
  		
	 	for (var i = nodelist.length-1; i >= 0 ; i--) { 
			  var newfocus = nodelist[i];
				if (canscroll(newfocus, dir) && hasscrollbars(newfocus))
				{
					scrollfocus = newfocus;
					return newfocus;
				}
 		}

	 return scrollfocus;
 }
  
document.documentElement.addEventListener("wheel", function(e){
    MouseScroll(e);
});
  
 document.documentElement.addEventListener("mousedown", function(e){
    if (scrollfocus)
      {
        scrollpositiondelta(scrollfocus, 0);
      }
});
  
  /*
document.documentElement.addEventListener("mousemove", function(e){
       mousemoved = true;
});*/

  //WEBKIT = 'webkitRequestAnimationFrame' in window;
  WEBKIT = document.compatMode == 'CSS1Compat' || document.compatMode == "BackCompat";
  
  //console.log("window: " + window);
  console.log("Smoothscroll loaded! Webkit: " + WEBKIT);
  
  $smoothscroll$ = true;
}

function LoadConfig()
{
  smoothness = GM_getValue( 'smoothness', smoothness );
  sensitivity = GM_getValue( 'sensitivity', sensitivity );
  acceleration = GM_getValue( 'acceleration', acceleration );
  refreshrate = GM_getValue( 'refreshrate', refreshrate );
  //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( 'refreshrate', document.getElementById('ss-refreshrate').value)
  //console.log(document.getElementById('ss-alternative_sesitivity_multiplier').checked)
  console.log("Config for smoothscroll saved!")
  
  location.reload();
}

function CloseConfig()
{
 	var configbar = document.getElementById("ss-configbar");
  configbar.style.display = 'none';  
}

function InitConfigmenu()
{
  	//console.log("Initiating smoothscroll config...");
    var configbar = document.createElement('div');
    configbar.setAttribute("id","ss-configbar");
  
  //<tr><td>Sensitivity</td><td><input type="number" id="ss-sensitivity" min="0" max="100" value="' + sensitivity + '"></td><td> Scroll sensitivity (duh)</td></tr>
 	
  	configbar.innerHTML = '<div style="display:block; width: 100%; height: auto; border: 0px solid #aaaaaa; background-color: grey;">Config page for smoothscroll (refresh page for changes to apply!) Made by: Creec Winceptor</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="10" value="' + smoothness + '"></td><td> Smoothness factor value (default 1.00)</td></tr><tr><td>Sensitivity</td><td><input type="number" id="ss-sensitivity" min="0" max="100" value="' + sensitivity + '"></td><td> Scroll sensitivity (default 1.00)</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 (default 1.00)</td></tr><tr><td>Refreshrate</td><td><input type="number" id="ss-refreshrate" min="1" max="100" value="' + refreshrate + '"></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.documentElement;
  	configbar.style.width = '100%';  
  	configbar.style.display = 'none';  
  	configbar.style.position = 'absolute';
	configbar.style.zIndex = '9999';
  	configbar.style.backgroundColor = 'white';
  
  	configparent.insertBefore(configbar, configparent.childNodes[0]);
  	
  	document.getElementById("ss-close").onclick = function() {CloseConfig()};
  
  	document.getElementById("ss-save").onclick = function() {SaveConfig()};
}

var ConfigSmoothscroll = function()
{
  console.log("opening");
  if (typeof $smoothscroll$ == 'undefined'){
    alert("Smoothscroll is not running properly on this page!");
    return;
  }

  var configbar = document.getElementById("ss-configbar");
  configbar.style.display = 'block';  
  window.scrollTo(0, 0);
  //ss$("html, body").animate({ scrollTop: 0 }, "slow");
  console.log("opening config...");
}

if (typeof $smoothscroll$ == 'undefined'){
	//console.log("Initiating smoothscroll...");
	InitSmoothscroll();	
  GM_registerMenuCommand("Smoothscroll config", ConfigSmoothscroll );
}
else
{
  console.log("Smoothscroll already loaded!");
}