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:
-
if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'')
-
&& location.hostname == this.hostname) {
-
var $target = $(this.hash);
-
$target = $target.length && $target
-
var targetOffset = $target.offset().top;
-
$('html,body')
-
return false;
-
}
-
}
-
});
-
});
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:
-
var targetOffset = $target.offset().top;
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):
That's all there is to it. Have fun.



September 16th, 2007 at 4:15 pm
Cool!
Patrick
September 16th, 2007 at 6:39 pm
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:
September 16th, 2007 at 7:31 pm
Excellent, Aaron! Thanks for turning that into a quick plugin. Very nice.
September 17th, 2007 at 2:23 am
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.
September 17th, 2007 at 8:32 am
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.
September 17th, 2007 at 3:06 pm
Thanks for the reports, Joel and Oli. Much appreciated. I updated the entry to fix those issues.
September 17th, 2007 at 7:36 pm
[...] Learning jQuery » Animated Scrolling with jQuery 1.2 (tags: jquery javascript howto webdesign web development design library animation animated scrolling) [...]
September 19th, 2007 at 9:51 pm
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!
September 20th, 2007 at 11:09 am
[...] 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 [...]
September 25th, 2007 at 1:58 am
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
September 25th, 2007 at 9:34 am
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
September 25th, 2007 at 4:37 pm
@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.September 29th, 2007 at 1:50 pm
Doesn't seem to work in Safari 3.0. There is no animation.
September 30th, 2007 at 6:21 am
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.
September 30th, 2007 at 7:56 am
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 !
September 30th, 2007 at 11:34 am
Glad to hear you got it working!
October 3rd, 2007 at 5:53 am
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?
October 3rd, 2007 at 12:15 pm
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 thereturn false;from the animate-scroll script. Just "view source" on the test page, and you should see it all there.October 4th, 2007 at 4:40 am
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?
October 4th, 2007 at 1:28 pm
Hi John,
You're right about the
return falsebit. 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.
October 4th, 2007 at 11:54 pm
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
October 5th, 2007 at 6:47 am
No worries Karl - I appreciate your efforts! I look forward to seeing if you can unearth the "holy grail" of smooth scrolling anchor links!
October 11th, 2007 at 9:54 pm
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!
October 12th, 2007 at 1:05 am
very nice work. its really very helpful for anyone.
October 13th, 2007 at 11:00 am
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;
}
}
});
});
October 14th, 2007 at 6:50 pm
@Brett, in the thread you refer to, Kelvin suggests replaceing
window.onloadwith$(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.
October 16th, 2007 at 6:21 pm
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>
October 16th, 2007 at 9:21 pm
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.
October 16th, 2007 at 11:22 pm
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!
October 16th, 2007 at 11:33 pm
Hey Ben,
I think I see the problem: You're not using jQuery 1.2+. You're using version 1.1.4.
October 17th, 2007 at 12:06 am
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?
October 18th, 2007 at 10:27 pm
[...] Read the rest of this great post here [...]
October 23rd, 2007 at 3:53 pm
hi, please give me some installation instructions. what do i need to do to get this working.
October 23rd, 2007 at 4:30 pm
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.
});
October 23rd, 2007 at 6:43 pm
@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!
October 23rd, 2007 at 7:13 pm
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.
October 24th, 2007 at 3:05 am
[...] more here [...]
November 15th, 2007 at 12:18 pm
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).
November 24th, 2007 at 11:08 pm
Very nice scrolling , high so
November 26th, 2007 at 7:00 pm
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
November 26th, 2007 at 8:44 pm
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.
December 15th, 2007 at 6:20 pm
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.
January 9th, 2008 at 11:22 am
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 thereturn false;like this:January 9th, 2008 at 1:17 pm
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.
January 9th, 2008 at 1:34 pm
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:
Hope that helps!
January 17th, 2008 at 10:03 pm
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.
January 29th, 2008 at 3:01 am
Nice work, Sean.
January 29th, 2008 at 3:19 am
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?
January 29th, 2008 at 10:30 am
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.
February 6th, 2008 at 7:36 pm
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!
February 12th, 2008 at 3:38 pm
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 exampleexample.htm#linkname?Thanks again!
February 12th, 2008 at 3:39 pm
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 exampleexample.htm#linkname?Thanks again!
Ps. Just now I notice ABDC seems to ask the same thing...
February 13th, 2008 at 3:27 pm
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.
February 25th, 2008 at 8:42 am
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
February 25th, 2008 at 9:52 am
Thanks for the heads up, scb! Apparently, I had deleted the
<script>reference at some point. It's back now, and it's working.March 1st, 2008 at 11:20 pm
good for you carl. been wondering why this is not working on my safari its a windows version
April 24th, 2008 at 3:49 pm
gran trabajo!, muy bueno
April 24th, 2008 at 5:31 pm
Bon article sur les différentes possiblité de jquery, merci
April 30th, 2008 at 10:55 am
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
June 25th, 2008 at 2:06 pm
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!