23

I want to smoothly scroll to an element without using jQuery – just pure javascript. I would like a generic function to be able to both scroll down and scroll up smoothly to a specific position in the document.

I know I can use the following in jQuery:

$('html, body').animate({
     scrollTop: $('#myelementid').offset().top
}, 500);

How would I do it with just javascript?

This is what I am trying to do:

function scrollToHalf(){
  //what do I do?
}
function scrollToSection(){
 //What should I do here?
}
<input type="button" onClick="scrollToHalf()" value="Scroll To 50% of Page">
    <br>
    <input type="button" onClick="scrollToSection()" value="Scroll To Section1">
    <section style="margin-top: 1000px;" id="section1">
      This is a section
</section>

In jquery I would do it like so:

html, body{
  height: 3000px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="button" onClick="scrollToHalf()" value="Scroll To 50% of Page">
<br>
<input type="button" onClick="scrollToSection()" value="Scroll To Section1">
<section style="margin-top: 1000px;" id="section1">
  This is a section
</section>
<script>
function scrollToHalf(){
  var height = $('body').height();
 $('html, body').animate({
         scrollTop: height/2
    }, 500);
}
function scrollToSection(){
 $('html, body').animate({
         scrollTop: $('#section1').offset().top
    }, 500);
}
</script>

EDIT: I would also like to be able to smooth scroll to a certain position on the page

EDIT: CSS solutions are also welcome (although I would prefer javascript solutions)

Unmitigated
  • 76,500
  • 11
  • 62
  • 80
  • `scrollIntoView` is a viable option for your case. Check out my [answer](https://stackoverflow.com/questions/48634459/scrollintoview-block-vs-inline) for a detailed explanation for its use. – Angel Politis Aug 04 '18 at 21:33
  • 5
    Modern browsers also have a pure CSS solution to the first part of your question: `scroll-behavior: smooth;`. Your JavaScript solution could then function as a fallback. – dwhieb Aug 04 '18 at 21:48
  • Does this answer your question? [Smooth scroll anchor links WITHOUT jQuery](https://stackoverflow.com/questions/17733076/smooth-scroll-anchor-links-without-jquery) – dippas Jun 19 '22 at 16:08

6 Answers6

48

To scroll to a certain position in an exact amount of time, window.requestAnimationFrame can be put to use, calculating the appropriate current position each time. setTimeout can be used to a similar effect when requestAnimationFrame is not supported.

/*
   @param pos: the y-position to scroll to (in pixels)
   @param time: the exact amount of time the scrolling will take (in milliseconds)
*/
function scrollToSmoothly(pos, time) {
    var currentPos = window.pageYOffset;
    var start = null;
    if(time == null) time = 500;
    pos = +pos, time = +time;
    window.requestAnimationFrame(function step(currentTime) {
        start = !start ? currentTime : start;
        var progress = currentTime - start;
        if (currentPos < pos) {
            window.scrollTo(0, ((pos - currentPos) * progress / time) + currentPos);
        } else {
            window.scrollTo(0, currentPos - ((currentPos - pos) * progress / time));
        }
        if (progress < time) {
            window.requestAnimationFrame(step);
        } else {
            window.scrollTo(0, pos);
        }
    });
}

Demo:

/*
   @param time: the exact amount of time the scrolling will take (in milliseconds)
   @param pos: the y-position to scroll to (in pixels)
*/
function scrollToSmoothly(pos, time) {
    var currentPos = window.pageYOffset;
    var start = null;
    if(time == null) time = 500;
    pos = +pos, time = +time;
    window.requestAnimationFrame(function step(currentTime) {
        start = !start ? currentTime : start;
        var progress = currentTime - start;
        if (currentPos < pos) {
            window.scrollTo(0, ((pos - currentPos) * progress / time) + currentPos);
        } else {
            window.scrollTo(0, currentPos - ((currentPos - pos) * progress / time));
        }
        if (progress < time) {
            window.requestAnimationFrame(step);
        } else {
            window.scrollTo(0, pos);
        }
    });
}
<button onClick="scrollToSmoothly(document.querySelector('div').offsetTop, 300)">
Scroll To Div (300ms)
</button>
<button onClick="scrollToSmoothly(document.querySelector('div').offsetTop, 200)">
Scroll To Div (200ms)
</button>
<button onClick="scrollToSmoothly(document.querySelector('div').offsetTop, 100)">
Scroll To Div (100ms)
</button>
<button onClick="scrollToSmoothly(document.querySelector('div').offsetTop, 50)">
Scroll To Div (50ms)
</button>
<button onClick="scrollToSmoothly(document.querySelector('div').offsetTop, 1000)">
Scroll To Div (1000ms)
</button>
<div style="margin: 500px 0px;">
DIV<p/>
<button onClick="scrollToSmoothly(0, 500)">
Back To Top
</button>
<button onClick="scrollToSmoothly(document.body.scrollHeight)">
Scroll To Bottom
</button>
</div>
<div style="margin: 500px 0px;">
</div>
<button style="margin-top: 100px;" onClick="scrollToSmoothly(500, 3000)">
Scroll To y-position 500px (3000ms)
</button>

For more complex cases, the SmoothScroll.js library can be used, which handles smooth scrolling both vertically and horizontally, scrolling inside other container elements, different easing behaviors, scrolling relatively from the current position, and more.

var easings = document.getElementById("easings");
for(var key in smoothScroll.easing){
    if(smoothScroll.easing.hasOwnProperty(key)){
        var option = document.createElement('option');
        option.text = option.value = key;
        easings.add(option);
    }
}
document.getElementById('to-bottom').addEventListener('click', function(e){
    smoothScroll({yPos: 'end', easing: easings.value, duration: 2000});
});
document.getElementById('to-top').addEventListener('click', function(e){
    smoothScroll({yPos: 'start', easing: easings.value, duration: 2000});
});
<script src="https://cdn.jsdelivr.net/gh/LieutenantPeacock/SmoothScroll@1.2.0/src/smoothscroll.min.js" integrity="sha384-UdJHYJK9eDBy7vML0TvJGlCpvrJhCuOPGTc7tHbA+jHEgCgjWpPbmMvmd/2bzdXU" crossorigin="anonymous"></script>
<!-- Taken from one of the library examples -->
Easing: <select id="easings"></select>
<button id="to-bottom">Scroll To Bottom</button>
<br>
<button id="to-top" style="margin-top: 5000px;">Scroll To Top</button>

Alternatively, you can pass an options object to window.scroll which scrolls to a specific x and y position and window.scrollBy which scrolls a certain amount from the current position:

// Scroll to specific values
// scrollTo is the same
window.scroll({
  top: 2500, 
  left: 0, 
  behavior: 'smooth' 
});

// Scroll certain amounts from current position 
window.scrollBy({ 
  top: 100, // could be negative value
  left: 0, 
  behavior: 'smooth' 
});

Demo:

<button onClick="scrollToDiv()">Scroll To Element</button>
<div style="margin: 500px 0px;">Div</div>
<script>
function scrollToDiv(){
var elem = document.querySelector("div");
window.scroll({
      top: elem.offsetTop, 
      left: 0, 
      behavior: 'smooth' 
});
}
</script>

If you only need to scroll to an element, not a specific position in the document, you can use Element.scrollIntoView with behavior set to smooth.

document.getElementById("elemID").scrollIntoView({ 
  behavior: 'smooth' 
});

Demo:

<button onClick="scrollToDiv()">Scroll To Element</button>
<div id="myDiv" style="margin: 500px 0px;">Div</div>
<script>
function scrollToDiv(){
    document.getElementById("myDiv").scrollIntoView({ 
      behavior: 'smooth' 
   });
}
</script>

Modern browsers support the scroll-behavior CSS property, which can be used to make scrolling in the document smooth (without the need for JavaScript). Anchor tags can be used for this by giving the anchor tag a href of # plus the id of the element to scroll to). You can also set the scroll-behavior property for a specific container like a div to make its contents scroll smoothly.

Demo:

html, body{
  scroll-behavior: smooth;
}
a, a:visited{
  color: initial;
}
<a href="#elem">Scroll To Element</a>
<div id="elem" style="margin: 500px 0px;">Div</div>

The CSS scroll-behavior property works with JavaScript as well when using window.scrollTo.

Demo:

html, body{
  scroll-behavior: smooth;
}
<button onClick="scrollToDiv()">Scroll To Element</button>
<div style="margin: 500px 0px;">Div</div>
<script>
function scrollToDiv(){
  var elem = document.querySelector("div");
  window.scrollTo(0, elem.offsetTop);
}
</script>

To check if the scroll-behavior property is supported, you can check if it exists as a key in the style of the HTML element.

var scrollBehaviorSupported = 'scroll-behavior' in document.documentElement.style;
console.log('scroll-behavior supported:', scrollBehaviorSupported);
Unmitigated
  • 76,500
  • 11
  • 62
  • 80
6

Consider using Element.scrollIntoView().

Kosh
  • 16,966
  • 2
  • 19
  • 34
4

Use this CSS property you switch scroll behavior to be smooth.

html {
  scroll-behavior: smooth;
}

This will also scroll smoothly default html navigation by hash <a href="#about"> to <section id="about">, and no js required here.

If you want to add own logic to scrolling consider this example

here I am scrolling not directly to a scroll target element but 90px higher consdering fixed header height.

TL;DR

document.querySelectorAll("nav a").forEach(function (a) {
        a.addEventListener("click", function (event) {
          event.preventDefault();
          const hash = event.target.getAttribute("href");
          const scrollTarget = document.querySelector(hash);
          
          // Some additional logic
          const headerHeight = 90;
          window.scrollTo(0, scrollTarget.offsetTop - headerHeight);
        });
      });
  • 1
    SO MUCH deprecated info online for this question and here is the answer in 2022 with 0 votes. Thanks @Arseniy Shelestyuk – Ben Racicot Mar 30 '22 at 15:00
3

As I mentioned in my comment, scrollIntoView is a good option to consider – that gets greater and greater browser support – when you try to scroll to a specified element such as what you are apparently trying to do with your scrollToSection function.

To scroll to the middle of the page you can set the scrollTop property of the body and/or the html element to half the difference of the scrollHeight of the body and the innerHeight of the window. Couple the above calculation with requestAnimationFrame and you are set.

Here's how you can incorporate the above suggestions in your code:

function scrollToHalf(duration) {
  var
    heightDiff = document.body.scrollHeight - window.innerHeight,
    endValue = heightDiff / 2,
    start = null;
    
  /* Set a default for the duration, in case it's not given. */
  duration = duration || 300;
  
  /* Start the animation. */
  window.requestAnimationFrame(function step (now) {
    /* Normalise the start date and calculate the current progress. */
    start = !start ? now : start;
    var progress = now - start;
    
    /* Increment by a calculate step the value of the scroll top. */
    document.documentElement.scrollTop = endValue * progress / duration;
    document.body.scrollTop = endValue * progress / duration;
    
    /* Check whether the current progress is less than the given duration. */
    if (progress < duration) {
      /* Execute the function recursively. */
      window.requestAnimationFrame(step);
    }
    else {
      /* Set the scroll top to the end value. */
      document.documentElement.scrollTop = endValue;
      document.body.scrollTop = endValue;
    }
  });
}

function scrollToSection(element) {
  /* Scroll until the button's next sibling comes into view. */
  element.nextElementSibling.scrollIntoView({block: "start", behavior: "smooth"});
}
#section1 {
  margin: 1000px 0;
  border: 1px solid red
}
<input type="button" onClick="scrollToHalf()" value="Scroll To 50% of Page">
<br>
<input type="button" onClick="scrollToSection(this)" value="Scroll To Section1">
<section id="section1">
  This is a section
</section>
Angel Politis
  • 10,955
  • 14
  • 48
  • 66
1

You can literally scroll any node object you want, with a simple polyfill like this:

Node.prototype.scroll = window.scroll

it will give you same access to scroll objects, but with any DOM element, you can use it like:

document.querySelector('.scrollable-div').scroll({
  top: 500, 
  left: 0, 
  behavior: 'smooth' 
});
Nodir
  • 19
  • 2
0

There are a lot of answers to this question already, but I thought I might share what I use.

The following allows you to smooth scroll to any position on the page, downwards or upwards, within a specified amount of time. I'm not sure if it's compatible with every browser, but I'm pretty sure it is. (Someone correct me if I'm wrong.)

Important Edit: Make sure you don't have html {scroll-behavior: smooth;} in your CSS. Otherwise, this won't work.

function scrollToInTime(element, duration) {
  const endPoint = document.querySelector(element).offsetTop,
    distance = endPoint - window.pageYOffset,
    rate = (distance * 4) / duration, // px/4ms
    interval = setInterval(scrollIncrement, 4) //4ms is minimum interval for browser

  function scrollIncrement() {
    const yOffset = Math.ceil(window.pageYOffset)

    if (
      (yOffset >= endPoint && rate >= 0) ||
      (yOffset <= endPoint && rate <= 0)
    ) {
      clearInterval(interval)
    } else {
      //keep in mind that scrollBy doesn't work with decimal pixels < 1 like 0.4px, so
      //if duration is too big, function won't work. rate must end up being >= 1px
      window.scrollBy(0, rate)
    }
  }
}

Here's a codepen as an example: https://codepen.io/isaac-svi/pen/xxZgPZp?editors=0110

isaacsan 123
  • 1,045
  • 8
  • 11