Animated Scrolling with jQuery 1.2

A few weeks ago I wrote about how to use jQuery and a couple modules from the Interface plugin suite to automatically have same-page links scroll to their target location when clicked (Animated Scrolling for Same-Page Links). Well, now that jQuery 1.2 is out, and I've successfully upgraded this site to it without a hitch, we can do the same thing with jQuery core alone.

Here is what the code looks like with the minor change:

JavaScript:
  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. });

Not a bad little adjustment when you consider that we're able to get rid of a plugin dependency.

One subtle change here is that we've removed the "@" symbol for the attribute selector, because jQuery 1.2 uses the CSS notation rather than the XPath. The more significant change is the way we're locating the target's position and animating the scroll to reach it:

JavaScript:
  1. var targetOffset = $target.offset().top;
  2. $('html,body').animate({scrollTop: targetOffset}, 1000);

First, we declare a targetOffset variable for the target's top offset position. This is now possible because the .offset() method from the Dimensions plugin has been moved into the core library.

Next, we animate the scrollTop property of the body and html elements to that targetOffset value, courtesy of the expanded jQuery fx module.

But why do we need to select both body and html? Well, Firefox and IE use body in quirks mode but html in standards mode. Our $('html, body') selector takes both situations into account. Of course, if you know your pages are running in standards mode (which they should), then you can drop the body (and the comma) from the selector.

Update

I've since written an entry outlining an improved script for animating same-page links. It also contains a link to Ariel Flesler's excellent scrollTo plugin.

Ease on down the page

Let's not stop there, though. While we're playing around with this, we can add an easing effect. All we need to do is reference the easing plugin and add our easing option to the .animate() method, like so:
.animate({scrollTop: targetOffset}, 1000, 'backout');
And if, as Dorian asked about in a comment on the previous tutorial, you want to scroll to a position 30 pixels above the target id, you can do so simply by subtracting 30 from $target.offset().top:
var targetOffset = $target.offset().top - 30;

Animate scrolling within an element

Here's something really fun: with the new .animate() functionality, we can animate the scrolling of an element that has its CSS overflow property set to "auto" or "scroll."
Let's say, for example, that we have a div with an id of "scrollable" and inside it we have a bunch of paragraphs. Something like this:

Paragraph 1: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Paragraph 2: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Paragraph 3: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Paragraph 4: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Now we want to animate the scroll to the third paragraph. Before we get too far in the explanation, let's try it:

For this one, we find the difference between the div's top offset and that of the third paragraph. Then we just scroll the div to that number. Just for kicks, we'll include the "bounceout" easing method as well.

Update

Based on feedback from Joel Birch and Oli in the comments below, I've changed the code below to take advantage of "relative animations," using the syntax available in jQuery 1.2.1. My initial tests showed that it's now working regardless of scroll-bar or font-size adjustments. Here is what it looks like now (not sure if storing the difference in a third variable was necessary, but it felt right to me):

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

That's all there is to it. Have fun.

60 Responses to “Animated Scrolling with jQuery 1.2”

  1. Patrick Wolf Says:

    Cool!

    Patrick

  2. Aaron Schmidt Says:

    Nice examples. The second one (two liner) is exactly what I was looking for and it works great. If anyone's interested, here's some quick code to extend it into jQuery.fn:

    
    jQuery.fn.extend({
      scrollTo : function(speed, easing) {
        return this.each(function() {
          var targetOffset = $(this).offset().top;
          $('html,body').animate({scrollTop: targetOffset}, speed, easing);
        });
      }
    });
    
    $('#my-el').scrollTo(1000);
    
  3. Karl Says:

    Excellent, Aaron! Thanks for turning that into a quick plugin. Very nice.

  4. Joel Birch Says:

    Very interesting indeed. Something I have always wondered about getting positions in this way is what happens if the text size is increased. I tried it on this page. Notice that the "animate scrolling within an element" example still scrolls to the originally calculated position despite the text for paragraph 3 appearing lower after text scaling.

  5. Oli Says:

    Nice example! Something little seems not to work with the top offset when you first move the scrollbar to any position and click on the "scroll to paragraph 3"-button afterwards. It does not jump to the right position and jumps forth and back.

  6. Karl Says:

    Thanks for the reports, Joel and Oli. Much appreciated. I updated the entry to fix those issues.

  7. napyfab:blog» Blog Archive » links for 2007-09-17 Says:

    [...] Learning jQuery » Animated Scrolling with jQuery 1.2 (tags: jquery javascript howto webdesign web development design library animation animated scrolling) [...]

  8. Joel Birch Says:

    Thanks so much for fixing that issue, Karl. I'm using Interface scrollTo all over the place, so this write-up will help me do it better and cleaner in future. Cheers muchly!

  9. Learning jQuery » Animated Scrolling for Same-Page Links Says:

    [...] 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 [...]

  10. enej bajgoric Says:

    Hi For some reason this doesn't really work for me. I have jQuery 1.2 . I am also trying it on a page that is simular to this.
    http://www.cssplay.co.uk/layouts/basics2.html
    I am trying to do something like haveamint.com any help would be much appreciated.
    sorry if this is not very clear

    Thanks in advance

  11. Till Says:

    hi,

    why the "return false"? it breaks the browser-history, and it works with "true" as well. i havent testet many browser thou.

    handy code however; i used it on my site:)

    -till

  12. Karl Says:

    @enej, I need more information to be able to help you. What exactly isn't working about it? Can you show me your page?

    @Till, yeah, that's a sticky one. Try removing the return false. You'll get a lot of back-button clicks that don't do anything except change the hash in the URL. There are ways around this sort of thing. Klaus Hartl put together a History plugin for jQuery. But that was outside the scope of this tutorial.

  13. Philip Meissner Says:

    Doesn't seem to work in Safari 3.0. There is no animation.

  14. Aysseline Says:

    It's very nice and it works for me but now I have installed Imagebox from Interface and your code stop to work :( If I remove interface.js it run but I really need Imagebox, I think that I need to use scroll of Interface now.

  15. Aysseline Says:

    Forget my last post, I find a solution. The problem with Interface site is that you can't download just file you need and when I have take Imagebox it give me a file with all Interface UI. I find on another site just files for Imagebox and now all work together. Really happy because I love your animated script. Thanks !

  16. Karl Says:

    Glad to hear you got it working!

  17. John Says:

    Would it be possible to include some instructions on how to implement this with the history plugin? I love this effect but not being able to use the back button to return to the previous area of the page breaks usability a bit too much for me.

    I've tried to get this working with Klaus Hartl's history plugin but no joy. I'm wondering if it's due to it not being compatible with jQuery 1.2?

  18. Karl Says:

    Hi John,

    I just tested this with Klaus's history plugin, and it seemed to work fine for me. Take a look at my test page. Click on the link for the "Comment Form" to do the animated scroll. Then hit your back button.

    You'll need to add a reference to jquery.history_remote.js, add $.ajaxHistory.initialize() inside $(document).ready(), and remove the return false; from the animate-scroll script. Just "view source" on the test page, and you should see it all there.

  19. John Says:

    Hi Karl,

    Thanks so much for the demo - but there's a problem. When I first implemented your script for the smooth scrolling, I noticed the comment from Till indicating the problem with the browser history and so I tried it with the "return false;" removed. This leaves the history intact but it makes the effect much less appealing - i.e. the page quickly jerks to the anchor and back, then it scrolls smoothly to anchor. This is what seems to happen on your test.php page too - i.e. I'm not sure the history plugin is doing anything and if it is - it's not necessary since removing the "return false;" enables the browser history anyway.

    The ideal situation would be a nice smooth scroll without the jerkiness, the history plugin adds the #anchor to the browser address bar and then a click of the back button returns the user to the original point in the page where they first clicked the anchor link.

    I wonder is this actually possible?

  20. Karl Says:

    Hi John,

    You're right about the return false bit. I should have known that. Strange that I didn't see the jerkiness when I tested. Maybe some browsers handle it better than others do. Anyway, I'll look into getting this working with the history plugin a bit more. When I tried the history plugin with jQuery 1.2.1 and the demo page that it comes with, it seemed to work fine, so I'm not sure the problem lies there. I'm getting an error when trying to build it into my script, so I might need to ask Klaus for some help.

    I should add that my instructions for using the history plugin above were woefully incomplete, showing my lack of understanding of how it worked. Sorry for the inconvenience.

  21. Brett Says:

    Just a note with how you initialise your code,
    $(document).ready(function(){
    Should be just function(){

    The reason being is that internet explorer does not reliably start your code each time if you use your method. So sometimes your scrollbars would appear in IE6, othertimes not! Not a good impression.

    Kevin luck was nice enough to point this code difference out to me while I was trying it out as well:

    http://groups.google.com/group/jquery-en/browse_thread/thread/fb17f6045c24cd9f/0a8f55a546a9d56c#0a8f55a546a9d56c

  22. John Says:

    No worries Karl - I appreciate your efforts! I look forward to seeing if you can unearth the "holy grail" of smooth scrolling anchor links! :)

  23. bhaarat Says:

    again, thanks a bunch!!!
    awaiting the 'load on scroll' (i think thats what it is called) with jquery. Like dzone has. loading 25 more results when user scrolling toward end of the page.

    u rule karl!

  24. rajendra Says:

    very nice work. its really very helpful for anyone.

  25. Anton S. Says:

    Nice! just switched my website from Prototype to jQuery. (BTW, jQuery scrolling works much more smooth, you can compare with identical page powered by Prototype.)

    Your piece of code creates problems when using with jQuery LightBox and probably with many other plugins, as it scrolls to top of the page when you click on links that have '#' in their href attribute.
    (Example : 'next' or 'prev' links of the lightboxes)

    You need to change your select expression to a[href*=#]:not([href=#])

    Here is the fixed version:

    $(document).ready(function(){
    $('a[href*=#]:not([href=#])').click(function() {
    if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'')
    && location.hostname == this.hostname) {
    var $target = $(this.hash);
    $target = $target.length && $target
    || $('[name=' + this.hash.slice(1) +']');
    if ($target.length) {
    var targetOffset = $target.offset().top;
    $('html,body')
    .animate({scrollTop: targetOffset}, 1000);
    return false;
    }
    }
    });
    });

  26. Karl Says:

    @Brett, in the thread you refer to, Kelvin suggests replaceing window.onload with $(function() {. The $(document).ready(function() { line in my script does the same exact thing as Kelvin's $(function() {. The only difference is that $(function() { is shorthand.

    @Anton. Sorry about that problem you experienced with lightbox. You're right that there is a conflict there in my script. Actually, I've already implemented a slightly more robust solution, and I plan to write an update entry on it soon. Cheers.

  27. ben Says:

    sorry the html didn't come up properly:

    <div id="nav">
    <a href="#one">one</a>
    <a href="#two">two</a>
    </div>

    <div id="one">
    </div>
    <div id="two">
    </div>

  28. Karl Says:

    Hi Ben,

    It looks fine to me. Are you including the jquery.js file in the <head>? I might need to see a sample page that is exhibiting the problem in order to help troubleshoot.

  29. ben Says:

    Yeh i'm adding the jquery.js file.....and i had other jquery things working on the same page. here's a link to the page - http://www.vocle.com/jquery/hor.html

    its probably something really obvious...so sorry in advance. thanks for the help Karl!

  30. Karl Says:

    Hey Ben,

    I think I see the problem: You're not using jQuery 1.2+. You're using version 1.1.4. :)

  31. ben Says:

    hahah that exactly the problem! i downloaded the script a while ago and am only learning it now.....i've upgraded it and got it going perfectly. thanks so much for your time - and thanks for the tutorials - they're great for learning how to use the scripts!

    is it possible to have the same scrolling effect go sideways? i'm thinking about making a site with just one really wide page and have it scroll sideways to each part, is that possible?

  32. target » Blog Archive » Comment on Animated Scrolling with jQuery 1.2 by Anton S. Says:

    [...] Read the rest of this great post here [...]

  33. josh Says:

    hi, please give me some installation instructions. what do i need to do to get this working.

  34. Marlon Daley Says:

    how do i get the bounce option to work when using this 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.
    });

  35. Ariel Flesler Says:

    @Marlon

    Get jQuery.easing and add, as third parameter of jQuery.fn.animate (line 11) the one you like. For example:

    $('html,body').animate({scrollTop: targetOffset}, 1000, "easeOutBounce" );

    By the way, this article is great!

  36. Karl Says:

    Ariel,

    Thanks a lot for answering the question. And I'm glad you like the entry. Be sure to check out the improved version, too.

  37. hot92 » Comment on Animated Scrolling with jQuery 1.2 by Ariel Flesler Says:

    [...] more here [...]

  38. Jim Says:

    I'm curious how I could integrate the scolling with your show/hide accordion tutorials?

    I've got a long list of accordian items - and when I open/close one - the page sticks at the bottom - but it would be nice to scroll back to the object that the user clicked on to open the div (h3).

  39. Mind Link Says:

    Very nice scrolling , high so ;)

  40. Wick Says:

    FYI I'm getting a javascript error caused by examples.js when the page loads, in both FF2 & IE7.

    FF: $target.offset() has no properties
    IE: 'offset().top' is null or not an object

  41. Karl Says:

    Hi Wick,

    Thanks a lot for the heads up on the JavaScript error. Turns out it was being caused by some mangled HTML in a comment someone posted. All should be good now. Thanks again.

  42. Tom Says:

    Thank you,

    i was searching for exactly this one :-)

    I never thought that JQuery is so easy to work with, but thanks to your blog, i managed it.

  43. Joe Lencioni Says:

    This is exactly what I was looking for. Thank you so much for putting this out there. I did, however, make a small improvement that adds the hash to the URL and allows you to use your back button. Simply add location.hash = this.hash; just before the return false; like this:

    $(document).ready(function() {
      $('a[href*=#]').click(function() {
        if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'') && location.hostname == this.hostname) {
          var $target = $(this.hash);
          $target = $target.length && $target || $('[name=' + this.hash.slice(1) +']');
          if ($target.length) {
            var targetOffset = $target.offset().top;
            $('html,body').animate({scrollTop: targetOffset}, 1000, 'easeOutQuad');
            location.hash = this.hash;
            return false;
          }
        }
      });
    });
  44. Joe Lencioni Says:

    Hmm, on further inspection, this causes a little bit of jumping around... You can add a timer delay to the hash change to eliminate the jumping, but then the back button unfortunately doesn't work. Oh well.

  45. Karl Says:

    Hi Joe,

    thanks for sharing your attempt to get the back button working with this. I actually implemented something similar a while ago (but using the improved script as my starting point). The trick is to add the hash to window.location in the callback of the .animate() method. Here are the relevant lines:

      		  $('html, body').animate({scrollTop: targetOffset}, 400, function() {
      			  if (!location.hash) window.location += target;
      		  });
    

    Hope that helps!

  46. Sean Says:

    Karl, adding it into the callback does provide the ability to click "back", but the window doesn't actually scroll back to where you were before the animation started. I used Joe's method, because while the screen does "jump" initially for about half a nano second (I can BARELY even notice it, and my computer is by no means awesome), the ability to hit back and scroll back to where you were is very useful.

    i also trimmed down the code quite a bit, but that's because I know all of my #hash links are internal. Also, that's part of the fun of jQuery, seeing how short you can make the code. :) Anyways here's what I ended up with, working good for me.

    
      $('a[href^=#]:not([href=#])').click( function() {
        var x = $(this.hash);
        if( x.length ) {
          $('html,body').animate( { scrollTop: x.offset().top }, 500 );
          location.hash = this.hash;
          return false;
         }
      });
    
  47. jody Says:

    Nice work, Sean.

  48. Matt Says:

    Hi, is there a way to only have these behaviors occur in a certain div, and have everything outside of that div behave normally when you click on an anchor?

  49. Karl Says:

    Hi Matt,

    Yes, you can certainly limit the onclick handler to a certain subset of anchors. It's all in the selector.

    For example, you could change $('a[href*=#]') to $('#mydiv a[href*=#]') to limit it to anchors inside the div that has an id of "mydiv."

    That said, you might want to take a look at the improvements I made to this script, or better yet, Ariel Flesler's ScrollTo plugin, which he says was inspired by my improved script.

  50. ABDC Says:

    Thank you VERY much for that tutorial. My site uses very long pages and a lot of inpage links so this helps to keep your orientation while navigating.

    How could I make the page scroll as well when coming from another page via a deep link? I think I would need to select the location, look for a # then execute the above code, but I have no idea how to manipulate the location like that!? Any help appreciated thanks!

  51. Schmappel Says:

    Hi Carl,

    Great tutorial, thanks a bunch! I have a little question though. At the moment it only works when clicking a # link. Would it be possible to make it work upon loading a remote page, say for example example.htm#linkname?

    Thanks again!

  52. Schmappel Says:

    Hi Carl,

    Great tutorial, thanks a bunch! I have a little question though. At the moment it only works when clicking a # link. Would it be possible to make it work upon loading a remote page, say for example example.htm#linkname?

    Thanks again!

    Ps. Just now I notice ABDC seems to ask the same thing...

  53. Eduardo Says:

    I am really new to jQuery and I am having a hard time figuring out how to implement this, exactily what files do I need and what code triggers the scrolling. Also the example doesn't work, I hit the button and it doesn't scroll.

  54. scb Says:

    The example for "Animate example within an element" doesn't work.
    Clicking on the button does not scroll the div. It doesn't produce any js error also.
    (I'm using Iceweasel/2.0.0.10 )
    Thanks for the script :)

  55. Karl Says:

    Thanks for the heads up, scb! Apparently, I had deleted the <script> reference at some point. It's back now, and it's working.

  56. redtube Says:

    good for you carl. been wondering why this is not working on my safari its a windows version

  57. ctraos Says:

    gran trabajo!, muy bueno

  58. Appel d'offre de l'Indicateur Says:

    Bon article sur les différentes possiblité de jquery, merci

  59. Martin Sarsini Says:

    Great, works perfectly. Infact I was trying with the old one (using the interface library) and apart from the fact that was giving an error with the dequeue function it was dependant on that library that I didn't needed apart for this scoll functionality.
    Thank you

  60. Ryan Says:

    Hi there. I'm new to jquery (and I'll warn you, I'm no programmer!), but I've gotten other jquery plugins to work properly recently. Could you indulge this probably-simple-to-understand-question-for-programmers? I'm trying to put a link in my footer and an anchor at the top of my #content div and provide smooth scrolling -- without constraining the size of my #content div.

    Put more simply, I don't want to smooth-scroll within a div, I want to smooth-scroll the whole page from bottom to top. (I know I've seen it done before, I just can't remember where.)

    Thanks!

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <pre> <em> <i> <li> <ol> <strike> <strong> <ul>

IMPORTANT: If you wish to post code examples, please wrap them in <code> tags. Multi-line code should be wrapped in <pre><code> </code></pre> Also, use &lt; instead of < and &gt; instead of > in the examples themselves. Otherwise, you could lose part of the comment when it's submitted.