67

I've been trying to add a smooth scrolling function to my site for a while now but can't seem to get it to work.

Here is my HTML code relating to my navigation:

<div id="nav-wrapper">
<div id="nav" class="navbar navbar-inverse affix-top" data-spy="affix" data-offset-top="675">
  <div class="navbar-inner" data-spy="affix-top">
    <div class="container">

      <!-- .btn-navbar is used as the toggle for collapsed navbar content -->
      <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </a>

      <!-- Everything you want hidden at 940px or less, place within here -->
      <div class="nav-collapse collapse">
        <ul class="nav">
            <li><a href="#home">Home</a></li>
            <li><a href="#service-top">Services</a></li>
            <li><a href="#contact-arrow">Contact</a></li>
        </ul><!--/.nav-->
      </div><!--/.nav-collapse collapse pull-right-->
    </div><!--/.container-->
  </div><!--/.navbar-inner-->
</div><!--/#nav /.navbar navbar-inverse-->
</div><!--/#nav-wrapper-->

Here is the JS code I've added:

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

<script>
    $(document).ready(function(e) {

        $('#nav').scrollSpy()
        $('#nav ul li a').bind('click', function(e) {
            e.preventDefault();
            target = this.hash;
            console.log(target);
            $.scrollTo(target, 1000);
        });
    });
</script>

For what it's worth, here is where I received info on what I've done so far, and here is my site in it's current form. If you can help me I'll bake you a pie or cookies or something. Thanks!

Community
  • 1
  • 1
Brian
  • 2,687
  • 9
  • 37
  • 39

8 Answers8

204

Do you really need that plugin? You can just animate the scrollTop property:

$("#nav ul li a[href^='#']").on('click', function(e) {

   // prevent default anchor click behavior
   e.preventDefault();

   // store hash
   var hash = this.hash;

   // animate
   $('html, body').animate({
       scrollTop: $(hash).offset().top
     }, 300, function(){

       // when done, add hash to url
       // (default click behaviour)
       window.location.hash = hash;
     });

});

fiddle

jerone
  • 16,206
  • 4
  • 39
  • 57
nice ass
  • 16,471
  • 7
  • 50
  • 89
  • 9
    This works nicely when all links are to anchors in the same page, but if external links are included, they will no longer work. For it to only operate on anchors and leave real links operational use: `$("#nav ul li a[href^='#']").bind('click'').bind(...)` instead. – Dologan May 14 '13 at 11:05
  • 1
    nice catch, @Dologan - I edited the result and also changed `bind` to `on`. – acme Jun 24 '13 at 09:00
  • I've got some strange behaviours after implementing this solution: 1) after implementing this code when I open main page (mysite.com/index.php) url changes to smth like this mysite.com/index.php#.Um_weWIyLYA (strange hash automaticly added to url after page loaded); 2) when clicking on menu page scrolls down, but hash in url doesn't change (previously it changes to anchor, i.e. mysite.com/index.php#contacts) – pupadupa Oct 29 '13 at 17:27
  • btw, I use bootstrap 3 – pupadupa Oct 29 '13 at 17:40
  • 1
    My problem number 1 was not in Bootstrap. AddThis plugin cause that problem. Here is is a solution: http://support.addthis.com/customer/portal/articles/1013558-removing-all-hashtags-anchors-weird-codes-from-your-urls# – pupadupa Oct 30 '13 at 17:51
  • 1
    @pupadupa: hash doesn't change in the URL because of `e.preventDefault()`. You can add the hash after the animation completes (see edit) – nice ass Oct 30 '13 at 18:22
  • @onetrickpony now it's give me undefined hash like this mysite.com/index.php#undefined. BTW I've wrap your code with $(document).ready(function() { /*your_c0de_here*/ }); – pupadupa Oct 31 '13 at 11:33
  • 9
    To solve undefinde problem I do this: 1) add this line `target = this.hash;` before `e.preventDefault();` 2) change line `window.location.hash = this.hash;` to `window.location.hash = target;` – pupadupa Nov 01 '13 at 12:46
  • 3
  • How do you account for extra space for a fixed navbar? `window.location.hash` governs the new url string, and the location. How can you set them independently? – ahnbizcad Feb 01 '15 at 02:29
  • Probably way easier to add a class to the `a` tags themselves and reference that jQuery selector instead, rather than trying to guess the nesting order of the nav elements. – brandonscript May 25 '16 at 22:35
15

If you have a fixed navbar, you'll need something like this.

Taking from the best of the above answers and comments...

$(".bs-js-navbar-scrollspy li a[href^='#']").on('click', function(event) {
  var target = this.hash;

  event.preventDefault();

  var navOffset = $('#navbar').height();

  return $('html, body').animate({
    scrollTop: $(this.hash).offset().top - navOffset
  }, 300, function() {
    return window.history.pushState(null, null, target);
  });
});

First, in order to prevent the "undefined" error, store the hash to a variable, target, before calling preventDefault(), and later reference that stored value instead, as mentioned by pupadupa.

Next. You cannot use window.location.hash = target because it sets the url and the location simultaneously rather than separately. You will end up having the location at the beginning of the element whose id matches the href... but covered by your fixed top navbar.

In order to get around this, you set your scrollTop value to the vertical scroll location value of the target minus the height of your fixed navbar. Directly targeting that value maintains smooth scrolling, instead of adding an adjustment afterwards, and getting unprofessional-looking jitters.

You will notice the url doesn't change. To set this, use return window.history.pushState(null, null, target); instead, to manually add the url to the history stack.

Done!

Other notes:

1) using the .on method is the latest (as of Jan 2015) jquery method that is better than .bind or .live, or even .click for reasons I'll leave to you to find out.

2) the navOffset value can be within the function or outside, but you will probably want it outside, as you may very well reference that vertical space for other functions / DOM manipulations. But I left it inside to make it neatly into one function.

Lee Taylor
  • 7,761
  • 16
  • 33
  • 49
ahnbizcad
  • 10,491
  • 9
  • 59
  • 85
8
$("#YOUR-BUTTON").on('click', function(e) {
   e.preventDefault();
   $('html, body').animate({
        scrollTop: $("#YOUR-TARGET").offset().top
     }, 300);
});
8
// styles.css
html {
    scroll-behavior: smooth
}

Source: https://www.w3schools.com/howto/howto_css_smooth_scroll.asp#section2

James Akwuh
  • 2,169
  • 1
  • 23
  • 25
4

If you download the jquery easing plugin (check it out),then you just have to add this to your main.js file:

$('a.smooth-scroll').on('click', function(event) {
    var $anchor = $(this);
    $('html, body').stop().animate({
        scrollTop: $($anchor.attr('href')).offset().top + 20
    }, 1500, 'easeInOutExpo');
    event.preventDefault();
});

and also dont forget to add the smooth-scroll class to your a tags like this:

 <li><a href="#about" class="smooth-scroll">About Us</a></li>
escolta13
  • 49
  • 5
3

I combined it, and this is the results -

$(document).ready(function() {
     $("#toTop").hide();

            // fade in & out
       $(window).scroll(function () {
                    if ($(this).scrollTop() > 400) {
                        $('#toTop').fadeIn();
                    } else {
                        $('#toTop').fadeOut();
                    }
                });     
  $('a[href*=#]').each(function() {
    if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'')
    && location.hostname == this.hostname
    && this.hash.replace(/#/,'') ) {
      var $targetId = $(this.hash), $targetAnchor = $('[name=' + this.hash.slice(1) +']');
      var $target = $targetId.length ? $targetId : $targetAnchor.length ? $targetAnchor : false;
       if ($target) {
         var targetOffset = $target.offset().top;
         $(this).click(function() {
           $('html, body').animate({scrollTop: targetOffset}, 400);
           return false;
         });
      }
    }
  });
});

I tested it and it works fine. hope this will help someone :)

AvidLearner
  • 4,123
  • 5
  • 35
  • 48
genisugt
  • 31
  • 1
  • 1
    While this code snippet may solve the question, [including an explanation](http://meta.stackexchange.com/questions/114762/explaining-entirely-code-based-answers) really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion. – JasonMArcher Jun 11 '15 at 22:09
0

What onetrickpony posted is okay, but if you want to have a more general solution, you can just use the code below.

Instead of selecting just the id of the anchor, you can make it bit more standard-like and just selecting the attribute name of the <a>-Tag. This will save you from writing an extra id tag. Just add the smoothscroll class to the navbar element.

What changed

1) $('#nav ul li a[href^="#"]') to $('#nav.smoothscroll ul li a[href^="#"]')

2) $(this.hash) to $('a[name="' + this.hash.replace('#', '') + '"]')

Final Code

/* Enable smooth scrolling on all links with anchors */
$('#nav.smoothscroll ul li a[href^="#"]').on('click', function(e) {

  // prevent default anchor click behavior
  e.preventDefault();

  // store hash
  var hash = this.hash;

  // animate
  $('html, body').animate({
    scrollTop: $('a[name="' + this.hash.replace('#', '') + '"]').offset().top
  }, 300, function(){

    // when done, add hash to url
    // (default click behaviour)
    window.location.hash = hash;

  });
});
Jonah Bishop
  • 12,279
  • 6
  • 49
  • 74
Patrick Hillert
  • 2,309
  • 4
  • 22
  • 37
0

with this code, the id will not appear on the link

    document.querySelectorAll('a[href^="#"]').forEach(anchor => {
        anchor.addEventListener('click', function (e) {
            e.preventDefault();

            document.querySelector(this.getAttribute('href')).scrollIntoView({
                behavior: 'smooth'
            });
        });
    });