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.

Update

This entry is deprecated. Please see the following for more current information:

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

[js]$(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); return false; } } }); });[/js]

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:

[js]var targetOffset = $target.offset().top; $('html,body').animate({scrollTop: targetOffset}, 1000); [/js]

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.

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, 'easeOutBack');

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 "easeOutBounce" 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):

[js]$(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'); }); });[/js]

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