23

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.

Adeel Imran
  • 13,166
  • 8
  • 62
  • 77

3 Answers3

90

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.

Manuel Otto
  • 6,410
  • 1
  • 18
  • 25
  • 5
    This seems the best implementation so far, nice and smooth. Thank you for taking the time out and look into this issue. Cheers :) – Adeel Imran Nov 09 '17 at 16:41
  • 4
    This solution is beautiful! <3 – Nicholas Mar 20 '18 at 11:42
  • 1
    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: https://gist.github.com/SamJakob/c9175a4c2440e1b14b0b8cf7d99d2d24 – NBTX Oct 22 '18 at 17:10
  • @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? – Manuel Otto Oct 22 '18 at 19:57
  • @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 that – NBTX Oct 22 '18 at 20:57
  • 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. – tobiasg Oct 31 '18 at 19:07
  • @tobiasg 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? – Manuel Otto Nov 01 '18 at 19:03
  • @ManuelOtto Cool! Yeah, it's annoying. Does it have anything to do with requireAnimationFrame or something? Is there another prefix for Safari now? – tobiasg Nov 01 '18 at 20:02
  • On Chrome console I get error: `[Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive. See `. 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? – Fred K Apr 11 '19 at 10:11
  • Hi @ManuelOtto, works nicely on Chrome, but not on Safari, 12.1.1. Is there any fix for that? –  Jun 17 '19 at 08:55
  • @Nikita it should now. – Manuel Otto Jun 17 '19 at 18:04
  • Thanks @ManuelOtto! As I understand it, the secret is in `Safari is the new IE` –  Jun 17 '19 at 21:41
  • @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. – Manuel Otto Jun 18 '19 at 22:04
  • 1
    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! – JordanBelf Jul 17 '19 at 19:51
  • 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 link – JordanBelf Jul 18 '19 at 03:09
  • @JordanBelf 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 thansk – Alexandru Gatea Jul 19 '19 at 11:46
  • 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? – Alexandru Gatea Jul 22 '19 at 08:32
  • I have been trying to search solution for a day. this is the perfect one! Thank you – mudin Aug 21 '19 at 04:48
  • 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. – C.L. Jan 13 '20 at 15:41
  • @C.L. 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 loop – Lynx May 10 '20 at 11:48
  • @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 [https://stackoverflow.com/questions/62170483/prevent-page-scrolling-when-scroll-inside-div-element] (Stackoverflow) Thanks – Raydoan Anik Jun 03 '20 at 10:25
  • 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. – TheCat Aug 21 '20 at 21:27
  • @ManuelOtto you are a hero – TheTrueTDF Sep 17 '20 at 09:05
  • 2
    @JordanBelf 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. – TheTrueTDF Sep 17 '20 at 09:36
  • 1
    @TheTrueTDF 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 https://jsfiddle.net/tw0xc1L2/ – JordanBelf Sep 18 '20 at 14:13
  • is this solution production worthy? does anybody know how jquery animates scrolling to achieve smooth scrolling? – oldboy Nov 10 '20 at 08:01
  • 1
    FYI ` ` is required for this to work. – Audi Nugraha Jan 23 '21 at 06:54
  • 1
    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? – Andrzej Dzirba Mar 12 '21 at 08:51
  • How can I use this function to scroll another container smoothly? – nonNumericalFloat Nov 15 '21 at 10:38
  • 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. – Miloš Đakonović Jun 10 '22 at 07:48
  • 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! – tsgrgo Aug 16 '22 at 12:49
  • 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. https://developer.mozilla.org/en-US/docs/Web/API/Element/DOMMouseScroll_event 2. https://developer.mozilla.org/en-US/docs/Web/API/Element/mousewheel_event 3. https://developer.mozilla.org/en-US/docs/Web/API/Element/wheel_event – Hassan Faghihi Feb 25 '23 at 06:19
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>
douxsey
  • 2,999
  • 1
  • 12
  • 11
  • 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. – N-ate Nov 09 '17 at 12:47
  • Anyway this can work with horizontal scroll? – gil hamer Dec 06 '22 at 11:40
1

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;
};
tnt-rox
  • 5,400
  • 2
  • 38
  • 52