59

I want to have 4 buttons/links on the beginning of the page, and under them the content.

On the buttons I put this code:

<a href="#idElement1">Scroll to element 1</a>
<a href="#idElement2">Scroll to element 2</a>
<a href="#idElement3">Scroll to element 3</a>
<a href="#idElement4">Scroll to element 4</a>

And under links there will be content:

<h2 id="idElement1">Element1</h2>
content....
<h2 id="idElement2">Element2</h2>
content....
<h2 id="idElement3">Element3</h2>
content....
<h2 id="idElement4">Element4</h2>
content....

It is working now, but cannot make it look more smooth.

I used this code, but cannot get it to work.

$('html, body').animate({
    scrollTop: $("#elementID").offset().top
}, 2000);

Any suggestions? Thank you.

Edit: and the fiddle: http://jsfiddle.net/WxJLx/2/

M P
  • 855
  • 5
  • 14
  • 18

13 Answers13

111

Question was asked 5 years ago and I was dealing with smooth scroll and felt giving a simple solution is worth it to those who are looking for. All the answers are good but here you go a simple one.

function smoothScroll(){
    document.querySelector('.your_class or #id here').scrollIntoView({
        behavior: 'smooth'
    });
}

just call the smoothScroll function on onClick event on your source element.

DOCS: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView

Note: Please check compatibility here

3rd Party edit

Support for Element.scrollIntoView() in 2020 is this:

Region            full   + partial = sum full+partial Support
Asia              73.24% + 22.75%  = 95.98%
North America     56.15% + 42.09%  = 98.25%
India             71.01% + 20.13%  = 91.14%
Europe            68.58% + 27.76%  = 96.35%

scrollintoview support 2020-02-28

surfmuggle
  • 5,527
  • 7
  • 48
  • 77
Sanjay Shr
  • 2,026
  • 2
  • 16
  • 17
100

Super smoothly with requestAnimationFrame

For smoothly rendered scrolling animation one could use window.requestAnimationFrame() which performs better with rendering than regular setTimeout() solutions.

A basic example looks like this. Function step is called for browser's every animation frame and allows for better time management of repaints, and thus increasing performance.

function doScrolling(elementY, duration) { 
  var startingY = window.pageYOffset;
  var diff = elementY - startingY;
  var start;

  // Bootstrap our animation - it will get called right before next frame shall be rendered.
  window.requestAnimationFrame(function step(timestamp) {
    if (!start) start = timestamp;
    // Elapsed milliseconds since start of scrolling.
    var time = timestamp - start;
    // Get percent of completion in range [0, 1].
    var percent = Math.min(time / duration, 1);

    window.scrollTo(0, startingY + diff * percent);

    // Proceed with animation as long as we wanted it to.
    if (time < duration) {
      window.requestAnimationFrame(step);
    }
  })
}

For element's Y position use functions in other answers or the one in my below-mentioned fiddle.

I set up a bit more sophisticated function with easing support and proper scrolling to bottom-most elements: https://jsfiddle.net/s61x7c4e/

zanderwar
  • 3,440
  • 3
  • 28
  • 46
Daniel Sawka
  • 1,360
  • 2
  • 9
  • 7
  • 2
    Just doing a little library based on your source, linking here in the credits. Thank you! – Dev_NIX Dec 14 '16 at 10:55
  • 1
    @Dev_NIX Glad I could help! Once I used [sweet-scroll](https://github.com/tsuyoshiwada/sweet-scroll) library but it somehow did not play well with React. You could reuse some API or code from that. – Daniel Sawka Dec 15 '16 at 19:49
32

Just made this javascript only solution below.

Simple usage:

EPPZScrollTo.scrollVerticalToElementById('signup_form', 20);

Engine object (you can fiddle with filter, fps values):

/**
 *
 * Created by Borbás Geri on 12/17/13
 * Copyright (c) 2013 eppz! development, LLC.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */


var EPPZScrollTo =
{
    /**
     * Helpers.
     */
    documentVerticalScrollPosition: function()
    {
        if (self.pageYOffset) return self.pageYOffset; // Firefox, Chrome, Opera, Safari.
        if (document.documentElement && document.documentElement.scrollTop) return document.documentElement.scrollTop; // Internet Explorer 6 (standards mode).
        if (document.body.scrollTop) return document.body.scrollTop; // Internet Explorer 6, 7 and 8.
        return 0; // None of the above.
    },

    viewportHeight: function()
    { return (document.compatMode === "CSS1Compat") ? document.documentElement.clientHeight : document.body.clientHeight; },

    documentHeight: function()
    { return (document.height !== undefined) ? document.height : document.body.offsetHeight; },

    documentMaximumScrollPosition: function()
    { return this.documentHeight() - this.viewportHeight(); },

    elementVerticalClientPositionById: function(id)
    {
        var element = document.getElementById(id);
        var rectangle = element.getBoundingClientRect();
        return rectangle.top;
    },

    /**
     * Animation tick.
     */
    scrollVerticalTickToPosition: function(currentPosition, targetPosition)
    {
        var filter = 0.2;
        var fps = 60;
        var difference = parseFloat(targetPosition) - parseFloat(currentPosition);

        // Snap, then stop if arrived.
        var arrived = (Math.abs(difference) <= 0.5);
        if (arrived)
        {
            // Apply target.
            scrollTo(0.0, targetPosition);
            return;
        }

        // Filtered position.
        currentPosition = (parseFloat(currentPosition) * (1.0 - filter)) + (parseFloat(targetPosition) * filter);

        // Apply target.
        scrollTo(0.0, Math.round(currentPosition));

        // Schedule next tick.
        setTimeout("EPPZScrollTo.scrollVerticalTickToPosition("+currentPosition+", "+targetPosition+")", (1000 / fps));
    },

    /**
     * For public use.
     *
     * @param id The id of the element to scroll to.
     * @param padding Top padding to apply above element.
     */
    scrollVerticalToElementById: function(id, padding)
    {
        var element = document.getElementById(id);
        if (element == null)
        {
            console.warn('Cannot find element with id \''+id+'\'.');
            return;
        }

        var targetPosition = this.documentVerticalScrollPosition() + this.elementVerticalClientPositionById(id) - padding;
        var currentPosition = this.documentVerticalScrollPosition();

        // Clamp.
        var maximumScrollPosition = this.documentMaximumScrollPosition();
        if (targetPosition > maximumScrollPosition) targetPosition = maximumScrollPosition;

        // Start animation.
        this.scrollVerticalTickToPosition(currentPosition, targetPosition);
    }
};
Geri Borbás
  • 15,810
  • 18
  • 109
  • 172
21

Smooth scrolling - look ma no jQuery

Based on an article on itnewb.com i made a demo plunk to smoothly scroll without external libraries.

The javascript is quite simple. First a helper function to improve cross browser support to determine the current position.

function currentYPosition() {
    // Firefox, Chrome, Opera, Safari
    if (self.pageYOffset) return self.pageYOffset;
    // Internet Explorer 6 - standards mode
    if (document.documentElement && document.documentElement.scrollTop)
        return document.documentElement.scrollTop;
    // Internet Explorer 6, 7 and 8
    if (document.body.scrollTop) return document.body.scrollTop;
    return 0;
}

Then a function to determine the position of the destination element - the one where we would like to scroll to.

function elmYPosition(eID) {
    var elm = document.getElementById(eID);
    var y = elm.offsetTop;
    var node = elm;
    while (node.offsetParent && node.offsetParent != document.body) {
        node = node.offsetParent;
        y += node.offsetTop;
    } return y;
}

And the core function to do the scrolling

function smoothScroll(eID) {
    var startY = currentYPosition();
    var stopY = elmYPosition(eID);
    var distance = stopY > startY ? stopY - startY : startY - stopY;
    if (distance < 100) {
        scrollTo(0, stopY); return;
    }
    var speed = Math.round(distance / 100);
    if (speed >= 20) speed = 20;
    var step = Math.round(distance / 25);
    var leapY = stopY > startY ? startY + step : startY - step;
    var timer = 0;
    if (stopY > startY) {
        for ( var i=startY; i<stopY; i+=step ) {
            setTimeout("window.scrollTo(0, "+leapY+")", timer * speed);
            leapY += step; if (leapY > stopY) leapY = stopY; timer++;
        } return;
    }
    for ( var i=startY; i>stopY; i-=step ) {
        setTimeout("window.scrollTo(0, "+leapY+")", timer * speed);
        leapY -= step; if (leapY < stopY) leapY = stopY; timer++;
    }
    return false;
}

To call it you just do the following. You create a link which points to another element by using the id as a reference for a destination anchor.

<a href="#anchor-2" 
   onclick="smoothScroll('anchor-2');">smooth scroll to the headline with id anchor-2<a/>
...
...  some content
...
<h2 id="anchor-2">Anchor 2</h2>

Copyright

In the footer of itnewb.com the following is written: The techniques, effects and code demonstrated in ITNewb articles may be used for any purpose without attribution (although we recommend it) (2014-01-12)

surfmuggle
  • 5,527
  • 7
  • 48
  • 77
  • Thanks, just saved me some time :) I also added to `smoothScroll({eID, padding = 0})` and then `stopY += padding;` after `let stopY = elmYPosition(eID);` to have some padding around element and not to scroll to exact element – Lukas Liesis Apr 14 '16 at 07:16
  • 2
    Executing string as function.... :( Why not just: `setTimeout(window.scrollTo.bind(null, 0, leapY), timer * speed);` – monners Sep 01 '16 at 01:50
13

Why not use CSS scroll-behavior property

html {
  scroll-behavior: smooth;
}

The browser support is also good https://caniuse.com/#feat=css-scroll-behavior

esnezz
  • 619
  • 1
  • 7
  • 19
  • 3
    Safari is like IE. Some people are still using it by inertia or lack of knowledge. – Adrian P. Sep 10 '21 at 17:57
  • 1
    We should be dropping browsers who don't have features like this. Or ship a polyfill if business forces us to support. Either way this is the answer to the OPs question in 2022. – Ben Racicot Mar 30 '22 at 14:59
12

You could also check this great Blog - with some very simple ways to achieve this :)

https://css-tricks.com/snippets/jquery/smooth-scrolling/

Like (from the blog)

// 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' 
});

// Scroll to a certain element
document.querySelector('.hello').scrollIntoView({ 
  behavior: 'smooth' 
});

and you can also get the element "top" position like below (or some other way)

var e = document.getElementById(element);
var top = 0;

do {   
    top += e.offsetTop;
} while (e = e.offsetParent);

return top;
aniltilanthe
  • 4,587
  • 1
  • 18
  • 17
6

For a more comprehensive list of methods for smooth scrolling, see my answer here.


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. To scroll to an element, just set the y-position to element.offsetTop.

/*
   @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:

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);
        }
    });
}

document.getElementById("toElement").addEventListener("click", function(e){
  scrollToSmoothly(document.querySelector('div').offsetTop, 500 /* milliseconds */);
});
document.getElementById("backToTop").addEventListener("click", function(e){
  scrollToSmoothly(0, 500);
});
<button id="toElement">Scroll To Element</button>
<div style="margin: 1000px 0px; text-align: center;">Div element
  <button id="backToTop">Scroll back to top</button>
</div>

The SmoothScroll.js library can also be used, which supports scrolling to an element on the page in addition to more complex features such as smooth scrolling both vertically and horizontally, scrolling inside other container elements, different easing behaviors, scrolling relatively from the current position, and more.

document.getElementById("toElement").addEventListener("click", function(e){
  smoothScroll({toElement: document.querySelector('div'), duration: 500});
});
document.getElementById("backToTop").addEventListener("click", function(e){
  smoothScroll({yPos: 'start', duration: 500});
});
<script src="https://cdn.jsdelivr.net/gh/LieutenantPeacock/SmoothScroll@1.2.0/src/smoothscroll.min.js" integrity="sha384-UdJHYJK9eDBy7vML0TvJGlCpvrJhCuOPGTc7tHbA+jHEgCgjWpPbmMvmd/2bzdXU" crossorigin="anonymous"></script>
<button id="toElement">Scroll To Element</button>
<div style="margin: 1000px 0px; text-align: center;">Div element
  <button id="backToTop">Scroll back to top</button>
</div>

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' 
});

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' 
});
Unmitigated
  • 76,500
  • 11
  • 62
  • 80
5

I've been using this for a long time:

function scrollToItem(item) {
    var diff=(item.offsetTop-window.scrollY)/8
    if (Math.abs(diff)>1) {
        window.scrollTo(0, (window.scrollY+diff))
        clearTimeout(window._TO)
        window._TO=setTimeout(scrollToItem, 30, item)
    } else {
        window.scrollTo(0, item.offsetTop)
    }
}

usage: scrollToItem(element) where element is document.getElementById('elementid') for example.

  • This worked perfectly for me. I just think it would be better with semicolons! – Vahid Amiri Sep 22 '16 at 21:25
  • just wanted to add - my aim was not to use any external libraries, there is no point to load huge jquery just for page scroll ;-) –  Jan 04 '17 at 12:11
  • Hey, Thanks for the solution. :) Can you please tell me what's the use of variable `window._TO`. I'm unable to figure what it does and why are we using `clearTimeout` . I have removed `clearTimeout` and it works perfectly fine for me. – Subham Tripathi Feb 10 '17 at 10:48
  • 1
    @Subham Tripathi if you call the function again before it finishes it will behave quite bad - because it will keep moving to the first point and if the second call want to move it to different point it will be just there and back - forever –  Feb 10 '17 at 11:10
4

Variation of @tominko answer. A little smoother animation and resolved problem with infinite invoked setTimeout(), when some elements can't allign to top of viewport.

function scrollToItem(item) {
    var diff=(item.offsetTop-window.scrollY)/20;
    if(!window._lastDiff){
        window._lastDiff = 0;
    }

    console.log('test')

    if (Math.abs(diff)>2) {
        window.scrollTo(0, (window.scrollY+diff))
        clearTimeout(window._TO)

        if(diff !== window._lastDiff){
            window._lastDiff = diff;
            window._TO=setTimeout(scrollToItem, 15, item);
        }
    } else {
        console.timeEnd('test');
        window.scrollTo(0, item.offsetTop)
    }
}
Andrzej Sala
  • 90
  • 1
  • 5
2

you can use this plugin. Does exactly what you want.

http://flesler.blogspot.com/2007/10/jqueryscrollto.html

Feedthe
  • 54
  • 2
1

Why not use this easy way

Native JS

document.querySelector(".layout").scrollIntoView({
  behavior: "smooth",
});
Ian
  • 74
  • 7
0

If one need to scroll to an element inside a div there is my solution based on Andrzej Sala's answer:

function scroolTo(element, duration) {
    if (!duration) {
        duration = 700;
    }
    if (!element.offsetParent) {
        element.scrollTo();
    }
    var startingTop = element.offsetParent.scrollTop;
    var elementTop = element.offsetTop;
    var dist = elementTop - startingTop;
    var start;

    window.requestAnimationFrame(function step(timestamp) {
        if (!start)
            start = timestamp;
        var time = timestamp - start;
        var percent = Math.min(time / duration, 1);
        element.offsetParent.scrollTo(0, startingTop + dist * percent);

        // Proceed with animation as long as we wanted it to.
        if (time < duration) {
            window.requestAnimationFrame(step);
        }
    })
}
Kuba Szostak
  • 356
  • 3
  • 5
-1

Smooth scrolling with jQuery.ScrollTo

To use the jQuery ScrollTo plugin you have to do the following

  1. Create links where href points to another elements.id
  2. create the elements you want to scroll to
  3. reference jQuery and the scrollTo Plugin
  4. Make sure to add a click event handler for each link that should do smooth scrolling

Creating the links

<h1>Smooth Scrolling with the jQuery Plugin .scrollTo</h1>
<div id="nav-list">
  <a href="#idElement1">Scroll to element 1</a>
  <a href="#idElement2">Scroll to element 2</a>
  <a href="#idElement3">Scroll to element 3</a>
  <a href="#idElement4">Scroll to element 4</a>
</div>

Creating the target elements here only the first two are displayed the other headings are set up the same way. To see another example i added a link back to the navigation a.toNav

<h2 id="idElement1">Element1</h2>    
....
<h2 id="idElement1">Element1</h2>
... 
<a class="toNav" href="#nav-list">Scroll to Nav-List</a>

Setting the references to the scripts. Your path to the files may be different.

<script src="./jquery-1.8.3.min.js"></script>
<script src="./jquery.scrollTo-1.4.3.1-min.js"></script>

Wiring it all up

The code below is borrowed from jQuery easing plugin

jQuery(function ($) {
    $.easing.elasout = function (x, t, b, c, d) {
        var s = 1.70158;  var p = 0; var a = c;
        if (t == 0) return b;
        if ((t /= d) == 1) return b + c;
        if (!p) p = d * .3;
        if (a < Math.abs(c)) {
            a = c;   var s = p / 4;
        } else var s = p / (2 * Math.PI) * Math.asin(c / a);
        // line breaks added to avoid scroll bar
        return a * Math.pow(2, -10 * t)  * Math.sin((t * d - s) 
                 * (2 * Math.PI) / p) + c + b;
    };            

    // important reset all scrollable panes to (0,0)       
    $('div.pane').scrollTo(0); 
    $.scrollTo(0);    // Reset the screen to (0,0)
    // adding a click handler for each link 
    // within the div with the id nav-list
    $('#nav-list a').click(function () {             
        $.scrollTo(this.hash, 1500, {
            easing: 'elasout'
        });
        return false;
    });   
    // adding a click handler for the link at the bottom
    $('a.toNav').click(function () { 
        var scrollTargetId = this.hash;
        $.scrollTo(scrollTargetId, 1500, {
            easing: 'elasout'
        });
        return false;
    });    
});

Fully working demo on plnkr.co

You may take a look at the soucre code for the demo.

Update May 2014

Based on another question i came across another solution from kadaj. Here jQuery animate is used to scroll to an element inside a <div style=overflow-y: scroll>

 $(document).ready(function () {
    $('.navSection').on('click', function (e) {
        debugger;
        var elemId = "";    //eg: #nav2
        switch (e.target.id) {
        case "nav1":
            elemId = "#s1";
            break;
        case "nav2":
            elemId = "#s2";
            break;
        case "nav3":
            elemId = "#s3";
            break;
        case "nav4":
            elemId = "#s4";
            break;
        }
        $('.content').animate({
            scrollTop: $(elemId).parent().scrollTop() 
                    + $(elemId).offset().top 
                    - $(elemId).parent().offset().top
        }, {
            duration: 1000,
            specialEasing: { width: 'linear'
                    , height: 'easeOutBounce' },
            complete: function (e) {
                //console.log("animation completed");
            }
        });
        e.preventDefault();
    });
  });
surfmuggle
  • 5,527
  • 7
  • 48
  • 77
  • In the mean time I made this fiddle: http://jsfiddle.net/WxJLx/15/.. it is working in fiddle, but not on the wordpress. Can you check this source code, if you can spot any problems? Thank you: view-source:http://anchovyluxury.com/yachts/services/ – M P Jul 19 '13 at 10:51
  • What have you tried to find the mistake? Are you familiar with the chrome dev tools - they make it super easy to spot errors: `$('a[href^="#"]').click(function(){` in line 360 of http://anchovyluxury.com/yachts/services/ Have you any more questions regarding how to scroll smoothly? – surfmuggle Jul 19 '13 at 17:30
  • I tried to put in that code which pops up window that jquery is working. And my code is working in the fiddle, so dont know where the problem is. – M P Jul 19 '13 at 20:03