Improved Animated Scrolling Script for Same-Page Links

read 133 comments

After posting the last entry on animated scrolling with jQuery 1.2, I realized that I had left out an important piece of code. Actually, I didn't discover it until someone notified me that another page on the site was broken. Can you spot the problem(s)? [Note: the problem is not in line 3. The syntax highlighter just can't handle the regular expression with two slashes in it ("//") and is incorrectly treating them as a comment mark.] See the answer below the code.

  1. $(document).ready(function(){
  2.   $('a[href*=#]').click(function() {
  3.     if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'')
  4.     && location.hostname == this.hostname) {
  5.       var $target = $(this.hash);
  6.       $target = $target.length && $target
  7.       || $('[name=' + this.hash.slice(1) +']');
  8.       if ($target.length) {
  9.         var targetOffset = $target.offset().top;
  10.         $('html,body')
  11.         .animate({scrollTop: targetOffset}, 1000);
  12.        return false;
  13.       }
  14.     }
  15.   });
  16. });

Answer: The animated scrolling script hijacks links that look like this: <a href="#">. A couple people confirmed in the comments that the script needed a bit more work, so I figured we could take one more pass at it.

By the way, even though we attached the click event handler to all links that have the "#" symbol anywhere in the href, the very next line ensures that the link is pointing to the same page — by checking for a match between location.pathname and this.pathname — and the line after that ensures that it's pointing to the same domain, by checking for a match between location.hostname and this.hostname. With this approach, we can accommodate same-page links whether they include a fully-qualified URL, a relative URL, or just the fragment identifier.

Continue Reading Below

Check for the Hash

Let's fix the problem with the <a href="#"> links. The first thing we have to do is see if there is actually something following the "#" symbol in the href. Apparently, if there is a lone "#" symbol, without any following characters, Firefox and Internet Explorer don't consider it a hash. Safari does, however. So, to avoid a false positive on <a href="#">, we need to first strip the "#" and then check if there is anything left. We can do so by adding this condition to the first if statement: && this.hash.replace(/#/,'')

Check for the Named Anchor

Since we're already changing the script, maybe it's a good time to make some of it more readable, too. This part with the "short-circuit" logic, using && and ||, makes me a little dizzy:

  1. var $target = $(this.hash);
  2. $target = $target.length && $target
  3. || $('[name=' + this.hash.slice(1) +']');
  4. if ($target.length) {

There is absolutely nothing wrong with this syntax. In fact, more advanced JavaScripters use it all the time. But I feel more comfortable using a simpler, more straightforward style. So, let's set two variables — one for a target ID and one for a target named anchor. We'll then use conditional (aka ternary) operators to set a third, $target, variable as the target ID if it's there, and if not, the target named anchor if it's there, and if not, false. Then we can just check if $target has some value (other than false):

  1. var $targetId = $(this.hash),
  2.   $targetAnchor = $('[name=' + this.hash.slice(1) +']');
  3. var $target = $targetId.length ? $targetId
  4.   : $targetAnchor.length ? $targetAnchor
  5.     : false;
  6. if ($target) {

Now it appears that the animated scrolling behavior will be attached to all same-page links and not break other stuff on the page.

Loop First, Bind Last

But there is another problem. Since we're still binding the .click() method to every link with "#" in it, even if it's appropriately avoiding applying the animation for some of those links, jQuery is still hijacking links that have an inline onclick handler (but, oddly, only the first time those links are clicked). To fix this problem, we can replace the .click() with .each(). Then we'll iterate through all links that have "#" somewhere in them, but place the conditions inside the loop so that we bind the click handler only after we've filtered out all the links that don't apply. Here is what the script looks like with the change:

  1. $(document).ready(function() {
  2.   $('a[href*=#]').each(function() {
  3.     if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'')
  4.     && location.hostname == this.hostname
  5.     && this.hash.replace(/#/,'') ) {
  6.       var $targetId = $(this.hash), $targetAnchor = $('[name=' + this.hash.slice(1) +']');
  7.       var $target = $targetId.length ? $targetId : $targetAnchor.length ? $targetAnchor : false;
  8.        if ($target) {
  9.          var targetOffset = $target.offset().top;
  10.          $(this).click(function() {
  11.            $('html, body').animate({scrollTop: targetOffset}, 400);
  12.            return false;
  13.          });
  14.       }
  15.     }
  16.   });
  17. });

Notice especially lines 2 and line 10. This change not only takes care of our problem, but it feels cleaner somehow, too. Is it more efficient? I don't know. Maybe someone else can tell us in the comments.

Normalize Directory Indexes

To be complete, we should probably take care of one more thing: the possibility that, on an "index" page, a link could point to "/path/index.htm" when the current location says "/path/" or vice versa. One way to "normalize" these index pages and links is to add a couple more .replace() methods to both sides of the equation in line 3.


Aman suggested in a comment below that I make this process DRYer, and kangax provided a great example. So we can write a filter function and apply it to both sides rather than repeating the three replaces on each side:

  1. function filterPath(string) {
  2.   return string
  3.     .replace(/^\//,'')  
  4.     .replace(/(index|default).[a-zA-Z]{3,4}$/,'')  // first additional replace
  5.     .replace(/\/$/,'');  // second additional replace
  6. }

The first additional .replace() will find a string represented by "index" or "default," followed by a dot, followed by any three or four letters at the end the pathname, and replace it with an empty string (i.e. remove it). The second one will replace a trailing slash with an empty string. As with chained jQuery methods, these regular-expression methods can be placed on separate lines to improve readability. Finally, we have a bullet-proof (I hope) animated scrolling script for same-page links:

  1. $(document).ready(function() {
  2.   function filterPath(string) {
  3.     return string
  4.       .replace(/^\//,'')  
  5.       .replace(/(index|default).[a-zA-Z]{3,4}$/,'')  
  6.       .replace(/\/$/,'');
  7.   }
  8.   $('a[href*=#]').each(function() {
  9.     if ( filterPath(location.pathname) == filterPath(this.pathname)
  10.     && location.hostname == this.hostname
  11.     && this.hash.replace(/#/,'') ) {
  12.       var $targetId = $(this.hash), $targetAnchor = $('[name=' + this.hash.slice(1) +']');
  13.       var $target = $targetId.length ? $targetId : $targetAnchor.length ? $targetAnchor : false;
  14.        if ($target) {
  15.          var targetOffset = $target.offset().top;
  16.          $(this).click(function() {
  17.            $('html, body').animate({scrollTop: targetOffset}, 400);
  18.            return false;
  19.          });
  20.       }
  21.     }
  22.   });
  23. });

If you try it out, let me know how it goes.

Update 2

Ariel Flesler has written an excellent ScrollTo plugin, which he says was inspired by this blog entry. Be sure to check out the demo.

Update 3

Someone called my attention to a problem that this script was having in IE and Opera. Not sure how I could have missed that, because I'm sure I tested it in both of those browsers. But never mind, I've come up with a little patch:

  1. $(document).ready(function() {
  2.   function filterPath(string) {
  3.   return string
  4.     .replace(/^\//,'')
  5.     .replace(/(index|default).[a-zA-Z]{3,4}$/,'')
  6.     .replace(/\/$/,'');
  7.   }
  8.   var locationPath = filterPath(location.pathname);
  9.   $('a[href*=#]').each(function() {
  10.     var thisPath = filterPath(this.pathname) || locationPath;
  11.     if (  locationPath == thisPath
  12.     && (location.hostname == this.hostname || !this.hostname)
  13.     && this.hash.replace(/#/,'') ) {
  14.       var $target = $(this.hash), target = this.hash;
  15.       if (target) {
  16.         var targetOffset = $target.offset().top;
  17.         $(this).click(function(event) {
  18.           event.preventDefault();
  19.           $('html, body').animate({scrollTop: targetOffset}, 400, function() {
  20.             location.hash = target;
  21.           });
  22.         });
  23.       }
  24.     }
  25.   });
  26. });

Apparently, IE doesn't see a hostname or pathname if a link's href attribute is set with JavaScript and contains only a hash (such as "#example"). So, I'm checking now for either a match or an absence of hostname and pathname.

I hope this change fixes the problem that Mike was having. Seems to work in my tests now. Oh, and I took the opportunity to improve the code a bit. Now, it has mild back-button support: while clicking on the back button doesn't produce the animated scrolling, it at least gets you back to the previous location.

Update 4

Ariel Flesler suggested that the problem a few people have mentioned regarding this script with Opera has to do with this line: $('html, body').animate({scrollTop: targetOffset}, 400); Here is a fix:

  1. $(document).ready(function() {
  2.   function filterPath(string) {
  3.   return string
  4.     .replace(/^\//,'')
  5.     .replace(/(index|default).[a-zA-Z]{3,4}$/,'')
  6.     .replace(/\/$/,'');
  7.   }
  8.   var locationPath = filterPath(location.pathname);
  9.   var scrollElem = scrollableElement('html', 'body');
  11.   $('a[href*=#]').each(function() {
  12.     var thisPath = filterPath(this.pathname) || locationPath;
  13.     if (  locationPath == thisPath
  14.     && (location.hostname == this.hostname || !this.hostname)
  15.     && this.hash.replace(/#/,'') ) {
  16.       var $target = $(this.hash), target = this.hash;
  17.       if (target) {
  18.         var targetOffset = $target.offset().top;
  19.         $(this).click(function(event) {
  20.           event.preventDefault();
  21.           $(scrollElem).animate({scrollTop: targetOffset}, 400, function() {
  22.             location.hash = target;
  23.           });
  24.         });
  25.       }
  26.     }
  27.   });
  29.   // use the first element that is "scrollable"
  30.   function scrollableElement(els) {
  31.     for (var i = 0, argLength = arguments.length; i <argLength; i++) {
  32.       var el = arguments[i],
  33.           $scrollElement = $(el);
  34.       if ($scrollElement.scrollTop()> 0) {
  35.         return el;
  36.       } else {
  37.         $scrollElement.scrollTop(1);
  38.         var isScrollable = $scrollElement.scrollTop()> 0;
  39.         $scrollElement.scrollTop(0);
  40.         if (isScrollable) {
  41.           return el;
  42.         }
  43.       }
  44.     }
  45.     return [];
  46.   }
  48. });

I put together a smooth-scroll jQuery plugin with a couple options, in case anyone is interested. Ariel Flesler's scrollTo plugin is another good option.

Anja Skrba has translated this article to Serbo-Croatian.

comment feed


  1. Aman Gupta

    Lines 4-6 are the same as lines 8-10. Surely there's a DRYer solution?

    • Marten

      Thanx for sharing!

      Is there a way to make it scroll a certain amount of pixels above the element with the id that it scrolls to? Thanx in advance!!

      • Hi Marten,

        Sure. Just change line 16 to this:

        var targetOffset = $target.offset().top - 20;

        Change 20 to the number of pixels you want to offset it.

      • kab

        Is it possible to specify the offset in 'em' instead of 'px'?

        • Mads Wolff

          Hi Kab. Using em to specify dimensions in javascript is quite easy.

          1. Make a div box somewhere in your html:

          <div id="ruler"></div>

          2. Set the width of #ruler to 1 em in your css:

          #ruler: 1em;

          3. Declare the following function and call it on document.ready:

          function set_em(){
          	em = $('#ruler').width();

          4. Now you can use the variable 'em' anywhere in your script like this:

          $('.container img').css({'height':6*em});
  2. Hi Aman,

    I'm not sure if there is a DRYer solution, because both location.pathname and this.pathname need to be run through the three replace methods in order to account for the possibility on an "index" page that the current URL and the link's href are represented differently: with or without a file name, such as index.html, and with or without the trailing slash. The first replace accounts for browser differences in including an initial slash in the pathname.

    Also, as I imply in the entry, lines 3 - 10 can be written on a single line, so maybe it just looks like more is going on than there actually is. If you can think of something DRYer and better, though, please let me know. I'm always eager to learn.

  3. Marcus T

    Great stuff! However, it's surprising you didn't provide an optional parameter to specify an easing algorithm other than the default linear. I've added it myself but perhaps you might want to do the same to your code published above.

  4. Hi Marcus,

    Sorry about that. It's really easy to use easing with the .animate() method. Just include an easing plugin and then add in the easing type as a parameter to .animate(). Something like this:

    $('html, body').animate(
      {scrollTop: targetOffset},
      {duration: 400, easing: 'easeInOutExpo'}

    You can also do the same thing with slightly different syntax, like this:

    $('html, body').animate(
      {scrollTop: targetOffset}, 400, 'easeInOutExpo'
  5. Verrrry good to Karl.. I don't know if you saw, but I made a plugin, inspired on your post. To scroll the window and overflowed elements as well... I pulled out an implementation of your "same-page-links-scrolling" using the plugin as someone asked for that. I must say all the credit goes to you ;)

  6. You could make the replacement to the page URI once and store it in a variable, then use jQuery.fn.filter with a function, and only to those passing the filter, apply the click. I think that might look cleaner.

  7. I might be failing to see something, but what's up with this "replace" repetition?

    Why not define a helper "filter" function to keep it "DRY" as Aman pointed out.

    $(document).ready(function() {
       function filter(string) {
          return string
       $('a[href*=#]').each(function() {
          if (filter(location.pathname) == filter(this.pathname)
             && 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;
  8. Thanks a lot, Kangax! That makes a lot of sense. I'll update the entry to include your function.

  9. I added this snippet of code, and it worked fine in IE and FF.

    function samePage( link ){
    return location.href.replace(location.hash,'') == link.href.replace(link.hash,'');

    return true or false.

  10. Hi,

    I've read all your posts relating to scrolling and I understand how it can scroll the page within the browser but I was wondering is there a way to modify it so that I can present a list of links above a "div" section that has an overflow set to scroll and animate the scrolling of this div?

    Thanks in Advance.

  11. Hi Matthew,

    Here is the code I demonstrated in the previous animated-scrolling tutorial. It triggers the scrolling from a single button to a specified place within the scrollable element, but changing it to a list o links triggering a scroll to multiple places within the div should be trivial.

    $(document).ready(function() {
      $('#scrollit').click(function() {
        var divOffset = $('#scrollable').offset().top;
        var pOffset = $('#scrollable p:eq(2)').offset().top;
        var pScroll = pOffset - divOffset;
        $('#scrollable').animate({scrollTop: '+=' + pScroll + 'px'}, 1000, 'bounceout');

    Note, you'll need to use an easing plugin with this example. If you'd rather not have an easing effect, just remove , 'bounceout' from the sixth line.

  12. Follow up to my last post:

    I applied ".parent()" to the click function to animate the div. It works great in FF2 but IE6 put the focus just below the section. Any idea's on how to fix this?

    Also I've pasted the entire code, for your reference, at:

    <script type="text/javascript" src="/js/jquery/jquery-1.2.1.pack.js"></script>
    <script type="text/javascript">
    $(document).ready(function() {
      function filterPath(string) {
    	return string
      $('a[href*=#]').each(function() {
    		if ( (filterPath(location.pathname) == filterPath(this.pathname))
    		&& (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() {
    				 $target.parent().animate({scrollTop: targetOffset}, 500);
    				 return false;
  13. Does this work (in your browsers)?

    $(document).ready(function() {
      function filterPath(string) {
    	return string
      $('a[href*=#]').each(function() {
    		if ( (filterPath(location.pathname) == filterPath(this.pathname))
    		&& (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 divOffset = $target.parent().offset().top;
    			 var pOffset = $target.offset().top;
    			 var pScroll = pOffset - divOffset;
    			 $(this).click(function() {
    				 $target.parent().animate({scrollTop: pScroll + 'px'}, 500);
    				 return false;

    Full Code at:

  14. Yes, it works for me in FF 2 Mac and IE 6 Windows. Try it here:

  15. By the way, I used $('a[hash]') in localScroll and it worked fine in IE, Opera and FF that I tested.
    Seems like href="#" gives empty string as hash, so even better!

  16. Millhouse

    This script works great, thank you for it!

    How could it be modified to show the clicked URL in the address bar?

  17. Hi Millhouse,

    Ariel Flesler has taken this script, converted it into a plugin, and improved upon it further. He just announced on the jQuery discussion list that the plugin can now show the clicked URL in the address bar. Take a look.

  18. Cedric Francois

    Cheers mate -works neatly for me...
    Thanks for this and happy xmas.!

  19. Andy

    This gives problems in Opera, using jQuery 1.2.1 and Opera 9.23.
    Some of the links work, other just don't go to the anchor, or got to the absolute top of the page.

    It works well in FF (2 and even 0.7) and IE 7.

  20. Ty (tzmedia)

    Ahhh, Karl, just the man I am looking for...
    Have you seen the buzz around this new teaser site for:
    The leaves use a parallax scrolling alignment effect when windows width is resized.
    Some parallax backgrounds along with easing effects, would be just killer with this very cutting-edge technique you have going here!!
    Searching the jQ user groups and to my surprise, I couldn't find any discussion at all of parallax scrolling.

  21. me too .. my client wants to work on opera and safari i really dont know why because the percentage of the user who use those browser are very small

  22. You are not unleashing the power of Jquery

  23. This is nice information though i am looking for css codes to display 5 links in same page. when a user click on these link they don't live that page and comes up the page ins the same page let me know please..

  24. Not sure what you mean by "css codes," but to have a link fetch information from another page and update the current page with it (without refreshing), you should look at jQuery's ajax methods. I think you'll find the load() method particularly helpful.

  25. abelafonte

    Is there a way to hilight the selected trigger once your scrolling stops?

  26. Gregory

    i tried to use this script to put a link in my footer to scroll back to the top of the page.
    clicking the link the first time effectively scrolls the page up, however scrolling the page down and clicking the link again has no effect.

    any explanation ?
    thx in advance

  27. Pretty good idea…

  28. Great work on this script, it's done really elegantly.

    I've got a large site that uses plenty of href="#" for 'back-to-top' links - However, with this updated script it won't animate back to the top without an explicit target id or name.

    Does anyone have any idea how I might get it to work in this scenario?


  29. Trev

    Just wanted to say how good the code is.

  30. axl

    Hi, I was looking for a way to scroll down to a certain div when the mouse hover an image. I wonder if this could be made using this logic. It's my first time using jQuery and I'm not even sure if this can be made and where to start.

    thanks for your help.

  31. Mike

    Hi, Karl! Nice work, now it works in IE7 too. Although, putting some *.png (even with pngfix script) in the page top (like header), slows down the script and near to the header it acts not so smooth, but that's IE problem I guess. In Opera, scrolling still doesn't work.

  32. tstorm

    Hi. I'm using this scroller in my work-in-progress wordpress theme and it's nice since it doesn't hijack other js, but I'm getting an error in Safari's inspector at line 17 and the scrolling only works in the downward direction for my two uppermost links. Any ideas? My site.

  33. Hi tstorm,

    Oddly enough, when I copied the html from your site and put it on my test server, it worked fine. Well, almost. The scrolling doesn't make it all the way down to the correct element. But I wonder if that might have to do with the HTML. Looks like it has some validation issues. Also, although Safari didn't throw the error you mentioned (on my site), it didn't like this inline onclick function: onclick="someFunc();return false". Not sure why that's in there, but it might be conflicting.

  34. mike

    Is there anyway to make this also scroll to a point when a new page is called?

    so like
    when you are coming from

    load up the new page and then scroll to it?

    • Laurence

      Hello Mike, I was wondering about the same thing. I see your post was a few years ago. I'm wondering if you managed to get it to do that in the end.
      Cheers, Laurence

  35. jean

    First thank you very much for a great tutorial my only problem is I don't seem to be able to make it scroll back to higher position using your piece of code :
    var targetOffset = $target.offset().top - 100;
    it just doesn't do anything in Firefox 3.0.3 as I havn't tested any other browser so far.
    Any ideas why ???
    Thanks for help
    Jean from France

  36. tstorm

    Hmm. That is weird that it's working perfectly on your test site. I got rid of that onclick function, which was a leftover from a different scroller I had tried, and I touched up the html a bit. But I'm still having the same problems with the scroller. I've been wracking my brain for what could account for the difference between your server and mine. No idea. But thanks for your help. Your scripts are the best.

  37. Hi, thanks for this technique; it works very well on almost every browser.. For some reason, the script fails in Google Chrome (version Instead of hitting the target, it gets lost, frequently scrolling in the opposite direction. I haven’t tested all of the specifics, but there is definitely an issue here..

  38. Adeel Shahid

    a note from site too :)

    i added to the very top of the document right after the body and then placed links for Top inside the page so that by click the link we can animatically go to the top of the page, but it goes to the top with only the bottom of the link test link just the slightest bit showing, so what i did is i placed,

    inside the body tag, which can also be done programatically now the correct behaviour to going to top work, tested in Firefoxe

  39. I have what seems to be an odd request. I am building a very tall page and I want the nav to smooth scroll down to the anchors. This script works great for normal text and html button links...but my nav is flash based and I can't get it to smooth scroll.

    If I use this action script on the flash button:
    it just jumps like normal and doesn't smooth scroll

    I know I can call a javascript with a flash button with this action script:

    But I can't figure out what "somefunction" and "something" to use in order to make the javascript work. I assume "something" would have to be my anchor (ex: "#anchor") right? But what about the "somefunction"

    I know the flash works I just don't know what from the javascript code needs to be put into the button

    Thanks in advance.

  40. alan


    Since no one else has answered you and in case you've not found the answer yourself here's my take on it - take Karl's code and turn it into a plain function rather than binding it to <a> click events like this:

    $(document).ready(function() {
      function scrollTo(scrollToId) {
        var $target = $('#' + scrollToId);
        var targetOffset = $target.offset().top;
        $('html, body').animate({scrollTop: targetOffset}, 400);

    Which is much simpler because we know the code is called with an id to an element on this page and so we don't need all the tests for different types of links.

    So if you had mark up like this:

    <object src="flash"></object>
    some text
    <div id="scrollTo1">text for 1st scroll to position</div>
    <div id="scrollTo2">text for 2nd scroll to position</div>
    <div id="scrollTo2">text for 3rd scroll to position</div>

    then your actionscript calls would be:

    Hope that helps,


  41. ashish

    var divOffset = $('#r320').offset().top;
    var jquerySelectedDivId='#'+selectDivId+'';
    var pOffset =$(jquerySelectedDivId).offset().top;
    var pScroll = pOffset - divOffset;
    var pScroll=0;
    $('#r320').animate({scrollTop: '+=' + pScroll + 'px'}, 1000);


    I am using div to div scrolling instead of link on same page...Nut this thing doesnt wrk on IE, pls help

  42. Dave Cabell

    Nice script, but seems to have very poor performance in FireFox 2.0. jQuery, as a whole, has noticeably poor performance in FF 2.0.

    • Hi Dave,
      That's a rather subjective comment and a broad generalization. Performance depends a lot on DOM structure, your coding style, and what you're trying to do. If you have specific concerns about jQuery's performance, I suggest you raise them at either the jquery-dev Google group or the jquery-en group.

  43. Dave Cabell

    This doesn't work in Safari. The animation doesn't work if you removed 'body' from the 'html,body' selector pair to make the top scrolling work in Opera. A quick browser check solved that issue. It's ugly but it's a fix.

              if($.browser.safari) {
                  $('body').animate({scrollTop: targetOffset}, 400, function() {
                    location.hash = target;
              } else {
                  $('html').animate({scrollTop: targetOffset}, 400, function() {
                    location.hash = target;
    • Mark

      I noticed the same thing. You can use the following code even though I have read that $ has been depreciated in JQuery 1.3 but still available for use?!

      var browserFix = 'html';  // default value for browsers
      if ($.browser.safari) { browserFix = 'body'; }
      $(browserFix).stop().animate({scrollTop: targetOffset}, 400, function() {
       location.hash = target;

      Maybe someone can tidy the code up a bit?

      • Mark

        The following line was meant to include a { char at the end:

        $(browserFix).stop().animate({scrollTop: targetOffset}, 400, function() {

  44. There are two bugs with this code:

    1. It crashes out if you have an anchor tag with a #link that doesn't have a matching id in the markup.

    2. It does not scroll to the correct location if you have any content in your page with a dynamic height.

    Bug fixes for both can be found here:

  45. in update 3 the if statement is missing the $

    if (target)
    instead of if ($target)

    Also, jquery 1.2.6 returns an object of length = 0 instead of null so this won't work.
    if ($target.length) works, though

  46. I just wanted to say your code is excellent. Awesome tutorials.
    Thank you for your hard work to help fellow coders.

  47. hi people. id like to ask you something regarding to a website im doing right now.

    First of all, I'm not a coder, developer or whatever it may be called, i'm a designer.. so i know very little code, enough to defend myself with css & html, but js is more difficult for me...

    I've managed to get a couple of functions running for the portfolio i'm trying to do for myself by checking with friends and google. They are in fact working, but i'm having another problem derived from what i'm doing.

    this is the test website i've done:

    if you check the source code, you will see two js functions.

    Now, if you click on the flash buttons and load the divs, you'll see there's an automatic scroll, and it works, but sometimes, mostly if you go on clicking and loading other divs, if then you try to go up again with a manual scroll, it'll get like stuck... going back and forth, up and down, till it gets unstuck and goes up definitely...

    i really don't know what's happening, but i assume it has to do with the 'scroller' function. It looks as if the scroller function is still active...
    I've tried to kill it.. but no luck.. maybe im not doing it right.

    can anyone help me with this??

    bythe way, i've tested it with FF and Safari on a Mac.


  48. Hi there,

    Firstly, thanks so much for the script, its ace!

    I am having on problem though. The first link within a page that is clicked never activates the scroll function, just jumps straight to the anchor as if the javascript wasn't on the page. After this it all works fine! Any clues as to why this might be happening?

    Many thanks,


  49. Giselle


    I'm new to jquery.

    I added jquery min, then I added the interface.js files into the same jquery min page. I tested an alert which works.

    Something is happening: the scroll bar jumps to the left and then back into place when it gets to the target. Am I missing something? Do I need something on my server? Thanks!


    • Hi Giselle,
      If you're using any version of jQuery after 1.1.4, it's not going to work as expected with interface.js. Interface hasn't been updated in a couple years now. Please give jQuery UI a look instead.

  50. is there a way to exclude certain elements?
    i.e. in wordpress - clicking on 'comments' goes to '#respond'

    i would like to exclude all elements with hrefs of '#respond' so that they still function to bring people to the comments area.


    • oops - i got it:

      i just used the 'not:' request.
      instead of:

  51. Danilo

    That is one awesome script. Thank you so much. However, I can't seem to find an elegant solution to the following problem:

    If I want to scroll to an ID near the bottom of the page that can't be scrolled to the very top of the viewport (because the page's end is reached instead - the element is lower than the viewport), then the swing easing stops abruptly, because the animation is calculated for scrolling the target element to the top of the viewport.

    For a particular element with known height I corrected the scrolling distance in my script appropriately, but I can't seem to get it done to elegantly check if every anchor clicked is scrollable to the top - and if not, corrects the scrolling distance according to the element's and viewport's height.

    I think it would improve your great script. Any tips would be greatly appreciated. Thanks.

  52. reloadro

    Hi. I've played around with this script , but i have some problems with implementing it the way i want . I'm using anchor links to describe content in different pages . The animation works fine if i'm on the same page but it doesn't work on different ones.

  53. TheUsualSuspect

    Hi there. I am using this perfect script and I added a little something to it. I just wanted to share the code since someone here asked for the same effect I was looking for, but never got an answer. So if you want to highlight the target anchor after scrolling just download the jQuery UI Plugin (somehow it did not work with the Color Animations Plugin) and add $(target).animate({opacity: 1.0}, 500)
    .animate({backgroundColor: "#99c0e1"}, 500)
    .animate({backgroundColor: "transparent"}, 500);
    after line 46.

    The snippet animate({opacity: 1.0}, 500) is a neat little trick (Also found on to delay the highlight effect till after scrolling is complete. Just set the animation time to the animation time of your scrolling. Simple, isn't it.

  54. ethos 2.0


    Got a question : How to use scroll effect with a form <select> to navigate between anchors ?
    Is it possible ?

    Tx by advance.

    • Hi ethos 2.o,
      Of course it's possible. The value of each option should be the id of the anchor you want to navigate to, preceded by the "#" sign. For example:

      <select id="goto">
        <option value="">go to ...</option>
        <option value="#anchor1">first item</option>
        <option value="#anchor2">second item</option>
        <option value="#anchor3">third item</option>

      Then, your jQuery script could be reduced to this:

      $('#goto').change(function() {
        var target = $(this).val();
        $('html, body').animate({scrollTop: target}, 400, function() {
          location.hash = target;

      Also, please read Update 4 above regarding a problem in Opera when using both html and body for the selector. For a workaround, see the scrollableElement function in my smooth-scroll plugin.

  55. Excellent tutorial. Have used this in many projects

  56. Just FYI, this has stopped working with jQuery 1.4

    • Hi Adrian,

      It seems to be working for me at this page. I just copied the code from this post and pasted it into that page.

      I tested in Mac FF 3.5.7 and Safari 4.x, as well as Windows IE6, 7, and 8.

      Is there a particular browser in which it isn't working for you?

      • Jon

        Using this with JQuery 1.4.2 throws out an error as follows:

        Expected identifier or string for value in attribue selector but found '#'.
  57. Matt

    Hi! I am having problems using this script in Opera 10.10, but it works in all other browsers. I am forced to use the following code:

    $(document).ready(function() {
      function filterPath(string) {
        return string
      $('a[href*=#]').each(function() {
        if ( filterPath(location.pathname) == filterPath(this.pathname)
        && 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}, 500);
               return false;

    If I use any of the updates, even those that are supposed to fix problems in Opera, it stops working on all browsers. I opened the demo in your (Karl's) last comment and that works fine in Opera. I'm not sure what is wrong in my code. Here is my page:

    Any help would be much appreciated. Thanks!

  58. Hi:

    Will this script work or can it be adapted for lateral motion on a page. In otherwords it would scroll from side to side. Any thoughts?

  59. Marek

    I've just implemented the script from github and it looks to be working when clicking on the a href it jumps to the specific area intended to jump to but it does not scroll up to the page just jumps up there with no animation.

    I'm using jquery-1.4.2.min.js is this the reason to why it's not working with the smooth scroll animation?

    Sorry i am complete noob when it comes to javascript

  60. Tyler

    For the initial problem presented on this page, why not just do what this script does: which is 'a[href^=#]:not([href=#])'

  61. Ryan

    Hey Karl,

    Great plugin. Very lightweight and worked great with jQuery 1.3.2. I've since upgraded to 1.4 and the functionality seems to be broken. Do you have an update available for your smoothScroll plugin? I'd rather not switch to Ariel's localScroll and scrollTo since it's more functionality than I need.

    Thanks much!

    • Hey Ryan,

      Sorry you're having problems with the plugin and jQuery 1.4. I'm not able to reproduce them, though. Can you give me some more information? Is it breaking in all browsers you've tested or one in particular? Do you have a test page that I can look at? Thanks.

    • Actually, I think I had forgotten to update the plugin on github. It should be all good to go now.

  62. Kolby

    Thanks for a great plugin - just a quick question. In chrome back-buttons works - but not in firefox. I'm not that experienced in coding so it might not be your plugin that should handle that. Is there an quick way of getting the "#xxx" after the http-address in when a link has been clicked?

    Right now I do get the #xxx but not before I have clicked the back-button (and only in chrome as mentioned).

    Any response would be greatly appreciated.

    Regards Kolby

    • Kolby

      I've read through all of the posts on this forum and others - and now know the answer to my own question :)

      Hope you manage to implement the functionality in a later revision.


      • Hey Kolby,
        Sorry I didn't get to you sooner.

        If you just want to add the hash to the url after the scroll, you can do that with the afterScroll option. Just do something like this:

          afterScroll: function() {
            window.location.hash = '#' + this.hash;

        If you want full back-button support, take a look at Ben Alman's BBQ plugin

  63. I noticed this didn't work when I clicked a link with a query string + hash (still went to hash target rather than going to the new page). I added + to these lines:

    var locationPath = filterPath(location.pathname +;


    pathMatch = opts.scrollTarget || (filterPath(link.pathname + || locationPath) === locationPath,

    And now it works.

  64. Nicely explained sample. I was able to create my own scroller quite easily.

  65. Thanks for the tutorial. It work very well so people don't get to disorientated when jumping around the page. Will try to implement this for my website.

  66. James Albuquerque

    why cant I scroll horizontally?

  67. Laurence

    Thanks for the great plug-in. I'm using the offset and it works fine for same-page links but when I link from another page I don't know how to reproduce the offset effect. Any help would be great.

  68. samueljesse

    I have a video player that plays new files through links of thumbnails on a page without re-freshing. Will this scroller work along side the video link?

  69. warpa

    Hello everyone,

    I am trying to use this code but I have a major issue.
    Instead of using an iframe, I use a div "test" on index.html and call all the other pages of my website in the "test" div.
    I paste this code inside all the pages.
    But when I click on a link in one of theses pages, the top and bottom margin disappear! However, the div "test" keeps the same left/right margin and size. I cannot see the contents of index.html that are above and below the div "test". Only the contents that are on the right and on the left of the div "test" are visible.

    Can someone help me please?

  70. Hi Karl (& everyone),

    First off I want to say that you've done a great job on this plugin, however, I have come across a slight problem. While it works great in every browser visually, for some reason, in a couple it does not seem to apply focus to the 'scrolled to' element instead retaining focus on the clicked anchor.

    This causes a problem for accessibility, when using a screen reader for example, as if a user clicks on a link, it is expected that the link will apply focus to the target element.

    I have found that the plugin works great in IE6, IE7, FF3.6 and Safari 5 but in Chrome 6.0.427.0 dev and Opera 10.5 the focus remains on the clicked anchor rather than shifting to the target element. My jQuery-fu isn't strong enough to figure that out, are you able to help?!

    Also, what is the best way to exclude links (by target or a class maybe?) from using the scrollTo plugin - I would like to prevent the plugin running pointlessly on my hidden accessibility links that only screen readers see.


  71. Fizgig

    Make an HTML page with a hundred <br/> tags in it, followed by <a href="#">click me</a>. Open the page, scroll down, and click the link. Like magic (and by "magic", I mean standard browser behavior), you'll be taken back to the top of the page. If there's an onclick event on the anchor, the onclick will fire, then you'll be taken to the top of the page.

    In other words, this script absolutely SHOULD hijack href="#", to animate the trip back to the top of the page.

    If you want a link that does nothing, use href="javascript://".

  72. An excellent snippet, I may even include it by default!

    To prevent is from messing with any other plugins I'm using that might require hashtags (tabs etc.) I may change the selector to rel="int" (internal).

  73. Daniel Farrell

    will this code work scrolling horizontal? if not what do I need to change in the code so that it can work?

    • For this code, you'd have to change all "top" to "left" and "Top" to "Left".

      My smooth-scroll plugin will work by passing in direction: "left" to the options map.

      The code above and my plugin (s0 far) take an either/or approach. You animate either scrollTop or scrollLeft. Ariel Flesler's ScrollTo plugin lets you scroll both left and top, simultaneously or sequentially.

      • Thank you for this answer. I've been looking for an alternative for horizontal scroll and your script works wonderfully.

        Once again thank you very much.

  74. Hi there, I was curious if this scrolling script has depreciated with the introduction of 1.5. I can't seem to get the smooth scroll to work properly even with your fantastic plugin.

  75. I've written a simplified variation of animated scrollintoview() jQuery plugin that can be used with dynamic scrollable pages (I needed it for Sharepoint that uses customizable masterpages so scrollable elements may not be known during development time).

    All you have to do is to call it on the element that needs to be in view. Plugin automatically finds the closest scrollable ancestor and scrolls it accordingly (with animation):


    Read all the details in my blog post:

  76. Is there a trick to getting this plug to work? Right now I have my anchors like so:


    I'm getting an error in firebug -- '$ is not defined'

    I've loaded jquery in. I think it has something to do with the named anchor. Any help would be appreciated.

    • If you're getting that error, it means that jQuery has not loaded by the time you load the other script. In Firebug, go to the Scripts tab and look at the jquery.js (or whatever you've named it) script. Make sure it has loaded properly.

  77. I cannot get this plugin to work for the life of me. The links that are supposed to scroll to different parts of the page are on the upper right. I'm using the latest version of this plugin and jQuery. Here is my page:

    Can anyone tell me what I'm doing wrong?


    • Hi Matt,

      It looks like there are a few issues with that page. First of all, the easing script you're using is broken. You should replace it with the one at . Second, you aren't calling the .smoothScroll() method anywhere. You need to do more than just include the plugin (but not much more). A simple one-liner like this should do the trick:

      $('.anchorlinks a').smoothScroll();

      Perhaps the biggest issue is that the links' hrefs are pointing to named anchors that don't have corresponding IDs. The name attribute for anchors is deprecated in HTML5, so I'm not supporting it in the plugin (though there is still a way to link to those if you long-hand it). If you give the anchors an id (e.g. <a name="foo" id="foo"></a>), it should work, as long as you make the other two changes I mentioned.

      Better than using named anchors at all would be to simply give each <h2> element an id. So, you'd have, for example, <h2 id="technology">Technology</h2>, and the link to #technology would scroll to that one.

      One more thing: Not sure if you know this, but you're loading an awful lot of scripts on that page, including two versions of jQuery (1.6.2 and 1.4.4). It would probably be worthwhile to look into that, although I suspect many of them are coming from WordPress plugins and I'm not sure how much control you have over what they spit out.

      I hope this helps you get things working on your site. I got a copy of your page working on my test server at . Let me know when you've had a chance to look at it and I'll remove it so it doesn't get crawled by googlebot, etc.

      • Wow thank you so much man! You went way out of your way here haha. I really appreciate your help!

        I went ahead with your suggestion and just removed all the anchors and instead assigned he h2 tag with an ID.

        Yes, unfortunately one of the downsides to WordPress is that you don't have a whole lot of control when it comes to plugins. I mean, you do but at the same time you don't... I could edit the plugins so that they don't include all these extra scripts, but the problem with that is when you upgrade, you lose all those changes. I really enjoy using WordPress and find it much easier/quicker to create websites. I just wish these plugin authors would do it right, and give you an option to include or not include their scripts!

        Thanks again man I got it working. I really appreciate your help with this!

      • By the way, I know you answered this already but wasn't sure if it has changed due to your plugin rewrites, but how would I make it so that there is about 20px of padding above where it scrolls to?

        • Hey Matt,
          Glad I could help! To change the offset, pass in an options map with an offset property set to the desired offset: $('.backtotop').smoothScroll({offset: -20}); .

          The Readme file at has a few more examples and a full list of options.

        • Hmmm... not really sure what that means. Wish I knew more about jQuery ;)

        • It just means that instead of writing smoothScroll() , you should write smoothScroll({offset: -20}). Does that make sense?

        • Ahhh perfect. Thanks man, I apologize for my inexperience with this stuff. Now I need to figure out how my lavalamp menu got broken!

        • The lava lamp plugin might be failing because you replaced the easing plugin that it was apparently using. Feel free to contact me (my first name at this site's domain) if you'd like some additional help with that. Not sure I'll have a whole lot of time, but if I do I'd be happy to take a look.

        • I thought that was the problem too until I went back to the previous easing plugin to find out that didn't fix the issue. I must have done something somewhere... Haha don't worry about it man, I'll get it fixed eventually ;)

          Thanks though, I really appreciate your assistance!


  78. Ming


    I was wondering if you've had a chance to review the very first comment at the top of the page?

    For some reason on the v4 or the last update you have of the code above, the changes to move the top 20 pixels down from the top doesn't work?

    var targetOffset = $target.offset().top - 20;

    What it does is move the page to the target -20pixels but then resits itself back to 0 again. So by the time it's at rest it's not -20 pixels but at the original position...?

    Any ideas as to how to fix that?


  79. Maud Ward

    I have added this code into my webpage that I am working on, but I am having issues, and am not sure how to fix them.

    I have a navigation to one side of the page in its own div container, and another div container with the anchor tags for scrolling. When I click something, the entire page slides smoothly just a little bit, but the div container that's supposed to be scrolling with the content still isn't smooth scrolling. I'm wondering if it's because they are in two different divs? Is there any way to fix this? If it would help I can upload the page to my server or something.

  80. Haitham

    I have fiddled with this for 2 days now, with no luck. I have a nested horizontal slider, which is supposed to slide horizontally to each pane based on ID, the code:

    Slider Test
    !window.jQuery && document.write(unescape('%3Cscript src="js/lib/jquery.js"%3E%3C/script%3E'));
    $(document).ready(function() {
    	jQuery.event.add(window, "load", resizeFrame);
    	jQuery.event.add(window, "resize", resizeFrame);
    	function resizeFrame() {
    		var h = $(window).height();
    		var w = $(window).width();
    		var sliderWidth = (w*10);
    		var paneWidth = w;
    		$('#resultWrite').text("width =" + w + "px; "+ "height =" + h + "px");
    		//setting sizes
    $(document).ready(function() {
      $('a[href*="#"]').live('click', function() {
    	if ( this.hash ) {
    	  $.bbq.pushState( '#/' + this.hash.slice(1) );
    	  return false;
      $(window).bind('hashchange', function(event) {
    	var tgt = location.hash.replace(/^#\/?/,'');
    	if ( document.getElementById(tgt) ) {
    	  $.smoothScroll({scrollTarget: '#' + tgt});
    About Us
    Our Services
    Our Work
    Follow Us

    I have also changed the the following in the smooth.scroll.js file as follows:

          direction: 'left',
          scrollElement: $('#slider').firstScrollable(),
          easing: 'easeInOutQuint',

    It just won't work for me. I went back and forth trying to figure out whats wrong, with no luck! :( Would you kindly help? Thank you so much!

  81. Nate

    is there a way to add 50px to where the top of where the scrolling stops? I have a div that is absolutely positioned to the top of my page and when the page scrolls to the div it is partially hidden behind that content. Thanks, love the functionality!

  82. Sebastian

    Hi Karl,

    great script, thank you!! I have a strange problem with the name/id-attribut. The script does only work with the "id" attribute. It's fine so far, but I have problems with the mobile-version on android 2.3. Seems that android does only accept the "name" attribute, if you add both (id and name), the anchor doesn't work. Is there a way to run the script with the "name"-attribute only?
    Thanke you!!

  83. Sagar

    can we scroll pages using keyboard keys..

  84. Peter H.

    Hello Karl, thanks for all your work on this script - very nice.

    One problem, which nowadays with so much mobile use really makes this script almost unusable, on iOS with a 'position: fixed;' div for the navigation menu, you lose clickability after the first scroll, until you manually scroll with a finger touch, then you can click-scroll again once only.

    I'm sure you already know about this but I was wondering if you have plans to work on it?

    • Hi Peter,

      I definitely see the problem you're referring to, and I can't figure out what is going on there. In fact, I see the same exact problem on a page where no JavaScript is included at all. For example, check out this page:

      Do you see the same problem there on iOS?

      I'm investigating.

    • I came up with an ugly hack that will work around this mobile webkit bug.

      * With smooth scroll:
      * Without smooth scroll:

      • Peter H.

        Karl, thanks for such a fast response. The 'stupid' fix works on the iPad and I assume on the iPhone too, though I haven't checked yet. I don't have an Android device to check on.
        I'm not at all knowledgeable about jquery and am like a bull in a china shop when I'm inside a jquery file. So am reluctant to touch anything in there aside from simple controls like speed change.
        I re-downloaded the zip package from your github pages and I'm a little confused about the various files there. The only html page that consistently works on the iPad is the "fixed-fix.html". The page "fixed.html" seems to exhibit the same problem as before the fix. Should I place your fix code inside script tags at the end of my html page (as in your fixed-fix.html)? Or can it go somewhere inside the 'jquery.smooth-scroll.js' file?
        One last question: can one use a different easing control apart from 'swing'?
        Thanks again.

        • The trouble with the naming is that all of the files are referring to position fixed. So, you're correct, the fixed-fix.html is the one you should use.

          You'll need to reference jQuery and the jquery.smooth-scroll.js plugin file in your html file. Then you can reference another script file that has the demo code with the "stupid" fix in it. Remember, you don't need to use the same variable names (e.g. "$stupid" and "mobileHack") and you don't need to use the same selector ( "ul.mainnav a" ). Also, I put the demo code in the html file so you can see it if you view source, but you're better off putting it in a separate file and referencing it like you do the jQuery file and the plugin file.

          Hope that all makes sense.

  85. Peter H.

    Karl, thanks a lot for fixed-fix. I couldn't possibly know whether the hack is ugly or stupid but it certainly works and I'll certainly use it!
    One last question (sorry, there's always another question): what is this line for?
    !window.jQuery && document.write(unescape('%3Cscript src="../lib/jquery/jquery.js"%3E%3C/script%3E'));
    Nothing appears to happen if I delete it, but perhaps it's there for some other browser that I haven't tested?

    • Yeah, it is a little stupid and ugly, but not nearly as stupid and ugly as the bug that is forcing us to do it in the first place. ;)

      As for that line, it's a fallback script load in case for some reason the jQuery script can't be loaded from Google's CDN. If the CDN can't be reached or times out or something, jQuery will be loaded from a local file instead.

  86. Erick

    this is awesome, is there a way to change the smooth scrolling to a kind of fade in effect? (:

  87. three

    help me. I don't have a clue beginner here.

    $(document).ready(function() {
    function filterPath(string) {
    return string
    var locationPath = filterPath(location.pathname);
    var scrollElem = scrollableElement('html', '');

    $('a[href*=#]').each(function() {
    var thisPath = filterPath(this.pathname) || locationPath;
    if ( locationPath == thisPath
    && (location.hostname == || !this.www.tringfernandez)
    && this.hash.replace(/#/,'') ) {
    var $target = $(this.hash), target = this.hash;
    if (target) {
    var targetOffset = $target.offset().top;
    $(this).click(function(event) {
    $(scrollElem).animate({scrollTop: targetOffset}, 400, function() {
    location.hash = target;

    // use the first element that is "scrollable"
    function scrollableElement(els) {
    for (var i = 0, argLength = arguments.length; i 0) {
    return el;
    } else {
    var isScrollable = $scrollElement.scrollTop()> 0;
    if (isScrollable) {
    return el;
    return [];


    I dont know why its stil not making a smooth scroll :(

7 Pings

  1. [...] I've posted a new entry about how to achieve the same effect (and more) using jQuery 1.2, without the need for any of the Interface plugin modules: Animated Scrolling with jQuery 1.2. [Posted an improved version Oct. 20, 2007] [...]

  2. napyfab:blog» Blog Archive » links for 2007-10-21

    [...] Learning jQuery » Improved Animated Scrolling Script for Same-Page Links (tags: jquery scroll scrolling animation animated javascript webdev webdesign web development design) [...]

  3. [...] Animated Scrolling with jQuery 1 and [...]

  4. [...] Learning jQuery is a great resource for doing this type of navigation. [...]

  5. [...] Improved Animated Scrolling Script for Same-Page Links » Learning jQuery - Tips, Techniques, Tutori... [...]

  6. jQuery Expand/Collapse Cookie HELP! - Jquery Home

    [...] { var localLink = $(container).find(o.localLinks); if (localLink.length) { // based on $(localLink).click(function() { var $target = $(this.hash); $target = $target.length && [...]

  7. [...] top of the page. This script is from Chris Coyer’s CSS Tricks website and originates from Karl Swedberg’s Learning JQuery [...]

Sorry, but comments for this entry are now closed.