Smooth vertical scrolling on mouse wheel in vanilla javascript?
Asked Answered
H

3

25

I am a huge fan for vanilla javascript, currently I am working on a project where I need to implement smooth scrolling on mouse wheel scroll. I want to implement this using vanilla JS. I found a jQuery snippet on doing some research which go like below.

$(window).on('mousewheel DOMMouseScroll', function(e) {
   var dir,
   amt = 100;

   e.preventDefault();
   if(e.type === 'mousewheel') {
     dir = e.originalEvent.wheelDelta > 0 ? '-=' : '+=';
   }
   else {
     dir = e.originalEvent.detail < 0 ? '-=' : '+=';
   }      

   $('html, body').stop().animate({
     scrollTop: dir + amt
   },500, 'linear');
});

Can anyone help me out as in how to implement a smooth scroll without using a helper library like jQuery or any other library.

There are many implementations that people have done in jQuery. But I want a best implementation that one can do in vanilla JS. That can implemented anywhere in React, Angular & Vue anywhere.

Hankypanky answered 30/10, 2017 at 8:47 Comment(0)
Y
94

How about this:

function init(){
	new SmoothScroll(document,120,12)
}

function SmoothScroll(target, speed, smooth) {
	if (target === document)
		target = (document.scrollingElement 
              || document.documentElement 
              || document.body.parentNode 
              || document.body) // cross browser support for document scrolling
      
	var moving = false
	var pos = target.scrollTop
  var frame = target === document.body 
              && document.documentElement 
              ? document.documentElement 
              : target // safari is the new IE
  
	target.addEventListener('mousewheel', scrolled, { passive: false })
	target.addEventListener('DOMMouseScroll', scrolled, { passive: false })

	function scrolled(e) {
		e.preventDefault(); // disable default scrolling

		var delta = normalizeWheelDelta(e)

		pos += -delta * speed
		pos = Math.max(0, Math.min(pos, target.scrollHeight - frame.clientHeight)) // limit scrolling

		if (!moving) update()
	}

	function normalizeWheelDelta(e){
		if(e.detail){
			if(e.wheelDelta)
				return e.wheelDelta/e.detail/40 * (e.detail>0 ? 1 : -1) // Opera
			else
				return -e.detail/3 // Firefox
		}else
			return e.wheelDelta/120 // IE,Safari,Chrome
	}

	function update() {
		moving = true
    
		var delta = (pos - target.scrollTop) / smooth
    
		target.scrollTop += delta
    
		if (Math.abs(delta) > 0.5)
			requestFrame(update)
		else
			moving = false
	}

	var requestFrame = function() { // requestAnimationFrame cross browser
		return (
			window.requestAnimationFrame ||
			window.webkitRequestAnimationFrame ||
			window.mozRequestAnimationFrame ||
			window.oRequestAnimationFrame ||
			window.msRequestAnimationFrame ||
			function(func) {
				window.setTimeout(func, 1000 / 50);
			}
		);
	}()
}
p{
  font-size: 16pt;
  margin-bottom: 30%;
}
<body onload="init()">
  <h1>Lorem Ipsum</h1>
  
  <p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
  
  <p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
  
  <p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
</body>

Use it by calling new SmoothScroll(target,speed,smooth)

Arguments:

  1. target: the element to be scrolled smoothly - can be a div or document
  2. speed: the amout of pixels to be scrolled per mousewheel step
  3. smooth: the smoothness factor, the higher the value, the more smooth.

Thanks to @Phrogz for the mousewheel normalization.

EDIT: Since Chrome 73 it is required to mark the event listener for the mousewheel event as non-passive in order to be able to call preventDefault() on it. Thanks to @Fred K for this.

Yaelyager answered 9/11, 2017 at 15:58 Comment(32)
This seems the best implementation so far, nice and smooth. Thank you for taking the time out and look into this issue. Cheers :)Hankypanky
This solution is beautiful! <3Chaisson
Hi I absolutely love your solution and used it in a NuxtJS project. I made some modifications and thought I'd share them; allows loading as an ECMAScript 2015 module, doesn't trigger on scrollbar (for some reason DOMMouseScroll was causing this), fixed an issue where a browser offset prevented you from scrolling all the way to the top / bottom: gist.github.com/SamJakob/c9175a4c2440e1b14b0b8cf7d99d2d24Randell
@SamJakob neat. unfortunately i have no experience in NuxtJS, so I cant give your module a test-twirl. But I'm wondering what the delta -= 1; is for. wouldn't that cause it to scroll upwards permanently?Yaelyager
@ManuelOtto odd - it actually seems to have no effect. (Not sure why that's in there.) I was having issues with it acting crazy when I was using a trackpad and I think I was trying to resolve it with thatRandell
Works like a charm for mousewheel. But the speeds are very different when scrolling with a Macbook Pro trackpad compared to an external mousewheel. Is there anyway to make the speed different depending on what kind of scroll device you are using? Also, it seems like the script doesn't work in Safari 12.Merideth
@Merideth I did not expect trackpads to trigger a mousewheel event...anyways, I've adjusted the code to deal with that. works on chrome & firefox using trackpad now. But why isn't it working on safari?Yaelyager
@ManuelOtto Cool! Yeah, it's annoying. Does it have anything to do with requireAnimationFrame or something? Is there another prefix for Safari now?Merideth
On Chrome console I get error: [Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive. See <URL>. Solved it changing these lines: target.addEventListener('mousewheel', scrolled, { passive: false }) target.addEventListener('DOMMouseScroll', scrolled, { passive: false }). Could you check if it works for you and edit your awesome answer?Spittle
Hi @ManuelOtto, works nicely on Chrome, but not on Safari, 12.1.1. Is there any fix for that?Intension
@Nikita it should now.Yaelyager
Thanks @ManuelOtto! As I understand it, the secret is in Safari is the new IEIntension
@Nikita yes mostly. Safari is the only browser where you need to set the scrollTop on the body element, yet read the clientHeight from the html element.Yaelyager
Hi! when clicking on an inline link #in-line-link the script goes to the #location but after that whenever you move the wheel it jumps back to the previous location for some reason. Does anyone know how to avoid that? Thanks!Gyatt
After many hours I managed to make it work with inline links. If anyone needs help let me know. Basically it involves updating the pos when someone clicks on an inline linkGyatt
@Gyatt can you please tell me how to solve the problem? I'm not a javascript wizard and I would like to solve this problem on my website. Many thanskSinh
Or is there anyone else that can give a hint on how could I update the pos on #inline-link and also on window resize?Sinh
I have been trying to search solution for a day. this is the perfect one! Thank youWithdrawn
Any ideas why sometimes even after you stop scrolling if (Math.abs(delta) > 0.5) fails and update function keeps looping ? How can I fix it ? (repeated animation frame function in background is a performance issue). BTW 'mousewheel' event is deprecated.Visibility
@Visibility IMO in update() method delta should be >=1 to trigger next frame. scrollTop seems to not accept values less than 1.0, and may run in infinite loopMasterpiece
@manuel Hi, it's working fine but I facing a problem when I tried to scroll inside a DIV element in the body. Here is my question I asked on [#62170983 (Stackoverflow) ThanksClimacteric
It stores the position by listening the wheel events. So if you scroll by dragging the scrollbar or if you use the arrow keys, THEN use the mouse wheel, it will actually scroll back to your previous position.Lanielanier
@ManuelOtto you are a heroBiernat
@Gyatt I am also interested. I have a problem that the scroll is jumpy when you first use the mouse and then the scrollbar or something else.Biernat
@Biernat here is the code I ended up using, I don't remember exactly and couldn't find the time yet to analyze it again, but I remember it involved updating the pos variable with the new pos once the user clicks on a link. Hope the code will help jsfiddle.net/tw0xc1L2Gyatt
is this solution production worthy? does anybody know how jquery animates scrolling to achieve smooth scrolling?Hebraism
FYI <!DOCTYPE html> is required for this to work.Lazaretto
Does anyone know how to handle the fact that trying to scroll the page with scroll bar or keyboard, and then moving mouse makes the script go back to previously browsed position?Boogiewoogie
How can I use this function to scroll another container smoothly?Relegate
This solution, while being awesome has other issues as well. The Page Up / Page Down keyboard scroll navigation attempt after regular scrolling fails (gets reversed/jammed). Tried with Windows FF, Chrome latest.Hickey
Honestly, the best answer I could find online. Beats every other smooth scroll solution or library. I had to modify it a bit, though, to match my needs but still, thanks a lot!Epigraphy
The non-standard DOMMouseScroll and the non-standard mousewheel are now deprecated, and can be replaced by the wheel event, said the MDN; I still did not implement it, and even after I'm not looking currently into the event data, to see if the event objects are different, but currently Safari iOS does not support 'wheel` so better to mix it with mousewheel . 1. developer.mozilla.org/en-US/docs/Web/API/Element/… 2. developer.mozilla.org/en-US/docs/Web/API/Element/… 3. developer.mozilla.org/en-US/docs/Web/API/Element/wheel_eventGorrian
P
2

The code you published is almost vanilla js. Just some ajustement

if you have some time Take a look at The wheel event

here the new thing will be the animate function

// Code goes here

document.addEventListener('wheel',function (event){
  //only vertical scroll
  if (event.deltaY > 0)
  {
    event.preventDefault();
    smoothScroll(document.documentElement,100,1000)
  }
})
function smoothScroll (domElement,pixel,delay)
{
  const intervalToRepeat = 25;
  const step = (intervalToRepeat * pixel) / delay;
  if ( step < pixel)
  {
    domElement.scrollTop += step;
    setTimeout(function (){
      smoothScroll(domElement,pixel - step,delay)
    },intervalToRepeat);
  }
  
  
}
  
<!DOCTYPE html>
<html>

  <head>
    <link rel="stylesheet" href="style.css">
    <script src="script.js"></script>
  </head>

  <body>
    <h1>Hello Plunker!</h1>
    
    <div style="width:400px;height:200px;" >
      lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some 
      lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some 
      lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some 
      lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some 
      <br>
      lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some 
      lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some 
      lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some 
      lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some 
      lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some 
      lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some 
      lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some 
      lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some 
      lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some 
      <br>
      lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some 
      lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some 
      lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some 
      lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some 
      lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some <br>
      lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some 
      lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some 
      lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some 
      lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some 
      lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some <br>
      lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some 
      lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some 
      lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some 
      lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some 
      lorem ipsum some lorem ipsum some lorem ipsum some lorem ipsum some 
      
    </div>
  </body>

</html>
Pukka answered 6/11, 2017 at 23:59 Comment(2)
Nice, but has issues scrolling up, if already scrolling down. Also, a busy page will cause issues with the scrolling. Rather than setting the delay based on the distance. Pick a small delay like 5ms then each time your function runs check the timestamp since last run and update the scroll position as a fraction of the planned scroll time and scroll distance, rather than just distance.Bachman
Anyway this can work with horizontal scroll?Training
C
0

A pure JavaScript onscroll event will work:

var container = document.getElementById('myScrollingSurface');
var lastY = 0;
container.onscroll = function () {
  doSomethingCool(container.scrollTop - lastY);
  lastY = container.scrollTop;
};
Corymb answered 8/11, 2017 at 7:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.