Smoothscroll

Smooth scrolling on pages using javascript

Per 06-09-2018. 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_link:Tampermonkey}.

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
// @author       Creec Winceptor
// @description  Smooth scrolling on pages using javascript
// @namespace https://greasyfork.org/users/3167
// @include     *
// @version 6
// ==/UserScript==

var Smoothscroll = {};

//dev
Smoothscroll.Debug = false;
Smoothscroll.Refreshrate = 60;

//settings
Smoothscroll.Smoothness = 0.5;
Smoothscroll.Acceleration = 0.5;

//scrolling and animation
function Timeout(element, newtimeout)
{
  if (newtimeout!=undefined)
  {
    var oldtimeout = element.ScrollTimeout;
    if (oldtimeout!=undefined)
    {
      clearTimeout(oldtimeout);
    }
    element.ScrollTimeout = newtimeout;
    return newtimeout;
  }
	else
  {
    var oldtimeout = element.ScrollTimeout;
    if (oldtimeout!=undefined)
    {
      return oldtimeout;
    }
    return null;
  }
}
function ScrollSubpixels(element, newvalue)
{
  if (newvalue!=undefined)
  {
    element.scrollsubpixels = newvalue;
    return newvalue;
  }
	else
  {
    var olddelta = element.scrollsubpixels;
    if (olddelta!=undefined)
    {
      return olddelta;
    }
    return 0;
  }
}
function ScrollPixels(element, newvalue)
{
  if (newvalue!=undefined)
  {
    element.scrollpixels = newvalue;
    
    ScrollSubpixels(element, 0);
    
    return newvalue;
  }
	else
  {
    var olddelta = element.scrollpixels;
    if (olddelta!=undefined)
    {
      return olddelta;
    }
    return 0;
  }
}
function AnimateScroll(target) {
  
  var updaterate = Math.floor(1000/(Smoothscroll.Refreshrate));
  
  var scrollsubpixels = ScrollSubpixels(target);
  var scrollpixels = ScrollPixels(target);

  var scrolldirection = 0;
  if (scrollpixels>0) {
    scrolldirection = 1;
  }
  if (scrollpixels<0) {
    scrolldirection = -1;
  }

  var scrollratio = 1-Math.pow( Smoothscroll.Refreshrate, -1/(Smoothscroll.Refreshrate*Smoothscroll.Smoothness));
  
  var scrollrate = scrollpixels*scrollratio;
  
  if (Math.abs(scrollpixels)>1) {
    
    var fullscrolls = Math.floor(Math.abs(scrollrate))*scrolldirection;
    var scrollsubpixelsadded = scrollrate - fullscrolls;

    var additionalscrolls = Math.floor(Math.abs(scrollsubpixels + scrollsubpixelsadded))*scrolldirection;
    var scrollsubpixelsleft = scrollsubpixels + scrollsubpixelsadded - additionalscrolls;

    ScrollPixels(target, scrollpixels-fullscrolls-additionalscrolls);
    ScrollSubpixels(target, scrollsubpixelsleft);
	
	  target.scrollTop += fullscrolls + additionalscrolls;

    Timeout(target, setTimeout(function() {

      AnimateScroll(target);
    }, updaterate));
  } else
  {
    ScrollPixels(target, 0);
  }
  
}

Smoothscroll.Stop = function(target) {
	if (target) {
		ScrollPixels(target, null);
		Timeout(target, null);
	}
}
Smoothscroll.Start = function(target, scrollamount) {
	if (target) {
		var scrollpixels = ScrollPixels(target);
		
		ScrollPixels(target, scrollamount);
		
		AnimateScroll(target);
	}
}

if (typeof module !== 'undefined') {
	module.exports = Smoothscroll;
}

//scroll target detection
function IsScrollable(element, dir)
{
  //TODO: problem with webkit, body not scrollable?
  if (element==document.body) {
    return false;
  }
  
	if (dir>0)
	{     
    if (Smoothscroll.Debug) {

      console.log("element.offsetHeight: " + element.offsetHeight);
      console.log("element.clientHeight: " + element.clientHeight);
      console.log("element.scrollTop: " + element.scrollTop);
      console.log("element.scrollHeight: " + element.scrollHeight);
    }
		return element.scrollTop+element.clientHeight<element.scrollHeight;
	}
	if (dir<0)
	{
		return element.scrollTop>0;
	}
  
	return true;
}

//scroll target detection backup
function IsScrollable0(element, dir)
{
  //TODO: problem with webkit, body not scrollable?
  if (element==document.body) {
    return false;
  }  

	var checkradius = 3; //pixels to try scrolling

	if (dir>0)
	{
		dir = checkradius;
	}
	if (dir<0)
	{
		dir = -checkradius;
	}

	var originalscroll = element.scrollTop;
	var testscroll = Math.round(originalscroll + dir);
	element.scrollTop = testscroll;

	var scrollable = Math.round(element.scrollTop)==testscroll;

  element.scrollTop = originalscroll;

	return scrollable;
}
function HasScrollbars(element)
{
  if (element==window || element==document) {
    return false;
  }
	//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 scrollbar0 = element==document.body || element==document.documentElement;
  
	var scrollbar1 = cStyle.overflow != "hidden" && cStyle.overflowY != "hidden";
	var scrollbar2 = cStyle.overflowY == "scroll" || cStyle.overflowY == "auto";
  
  var scrollbar3 = cStyle.maxHeight<cStyle.height;

	//body or html always have scrollbars
	
  var scrollbar4 = cStyle.overflow != "hidden" && cStyle.overflowY != "hidden";

	return scrollbar0 || (scrollbar1 || scrollbar2) && scrollbar3;
}
function GetTarget(e) {
  var direction = e.deltaY;
  var nodes = e.path;
  
  if (Smoothscroll.Debug) {
    console.log("nodes: ");
    console.log(nodes);
  
    console.log("target: ");
  }
  
  for (var i=0; i<(nodes.length); i++) { 
    var node = nodes[i];
    
    if (Smoothscroll.Debug) {
      console.log(node);
    }
    
    var scrollable_check = IsScrollable(node, direction);
    if (Smoothscroll.Debug) {
      console.log("scrollable_check: " + scrollable_check);
    }
    
    var scrollbar_check = HasScrollbars(node);
    if (Smoothscroll.Debug) {
      console.log("scrollbar_check: " + scrollbar_check);
    }
    
    if (scrollable_check && scrollbar_check)
    {
      if (Smoothscroll.Debug) {
        console.log("true");
        
      }
      return node;
    }
  }
  if (Smoothscroll.Debug) {
    console.log("false");

  }

  return null;
}



//mouse event scroll handlers
function StopScroll(e) {
  var nodes = e.path;
  
  for (var i=0; i<nodes.length; i++) { 
    var node = nodes[i];
    Smoothscroll.Stop(node);
  }
}
function StartScroll(e, target) {

  if (e.defaultPrevented)
  {
    return true;
  }
  else
  {
	var direction = e.deltaY;

	var scrollpixels = ScrollPixels(target);

	var accelerationratio = Math.sqrt(Math.abs(scrollpixels/direction*Smoothscroll.Acceleration));

	var acceleration = Math.round(direction*accelerationratio);

	Smoothscroll.Start(target, scrollpixels + direction + acceleration);

    e.preventDefault();
  }
}



//mouse event call handlers
function WheelEvent(e) {
  var target = GetTarget(e);
  
  if (target) {
    StartScroll(e, target);
  }
}
function ClickEvent(e) {
  StopScroll(e);
}



//init function
function Init()
{

  if (window.top != window.self) {
    //console.log("Smoothscroll: ignoring iframe");
    return null;
  }
  if (window.Smoothscroll && window.Smoothscroll.Loaded) {
    //console.log("Smoothscroll: already loaded");
    return null;
  }
  
  document.documentElement.addEventListener("wheel", function(e){
    WheelEvent(e);
    
    if (Smoothscroll.Debug) {
      console.log(e);
    }
  });

  document.documentElement.addEventListener("mousedown", function(e){
    ClickEvent(e);
    
    if (Smoothscroll.Debug) {
      console.log(e);
    }
  });
  
  window.Smoothscroll = Smoothscroll;
  window.Smoothscroll.Loaded = true;
  console.log("Smoothscroll: loaded");
}
Init();