Smooth Scrolls


When developing sophisticated web applications there are a myriad of effects that can be employed and combined to produce impressive functionality that improves the user experience. Care must be taken, of course, to ensure we're not improperly or overusing a certain functionality or effect. What is appropriate for one project may be inappropriate for another, so as developers and designers it's our job to determine when, where and why we utilize the effects and functionality that we do.

Getting Started


Alright, let's jump in and learn how to create some basic bi-directional smooth scrolling functionality that's cross-browser compliant. I'm sure, as with most things, there are at least a dozen perfectly valid approaches. If you've already written something for this, considering sharing your code with us in the comments.

My approach utilizes 3 functions which:
  1. determine the visible top of page Y coordinate;
  2. determine the destination elements Y coordinate; and
  3. perform the scroll, dynamically adjusting the speed based on distance.

You can download the attachment to see this approach in action.

currentYPosition


Determining the point on the Y Axis at which the scrolling has to start (starting coordinate) is the first thing we need to do. The point we want is the visible top of page coordinate, which takes into account any scrolling the user has done. We can then use this number, accompanied with the destination coordinate covered below to determine the distance between the starting and stopping points. This difference is then used to determine the scroll speed and perform the actual scrolling, whether scrolling up or down.

  1. function currentYPosition() {
  2.     // Firefox, Chrome, Opera, Safari
  3.     if (self.pageYOffset) return self.pageYOffset;
  4.     // Internet Explorer 6 - standards mode
  5.     if (document.documentElement && document.documentElement.scrollTop)
  6.         return document.documentElement.scrollTop;
  7.     // Internet Explorer 6, 7 and 8
  8.     if (document.body.scrollTop) return document.body.scrollTop;
  9.     return 0;
  10. }


elmYPosition


elmYPosition( string elementID )

Once we have the starting coordinate, we need to determine the destination coordinate. We can do this by assigning an ID to the destination element, whether it be a <div> tag, <span>, <img> or whatever. The following function expects the destination elements ID as its only parameter.

  1. function elmYPosition(eID) {
  2.     var elm = document.getElementById(eID);
  3.     var y = elm.offsetTop;
  4.     var node = elm;
  5.     while (node.offsetParent && node.offsetParent != document.body) {
  6.         node = node.offsetParent;
  7.         y += node.offsetTop;
  8.     } return y;
  9. }


So how does this function work, exactly? Well, say the destination element is half way down the page. What this function does is loop through the offsetParents, adding the offsetTop values to the y variable until it arrives at the top of the page. This will compute the elements true Y position.

smoothScroll


smoothScroll( string elementID )

Now that we're able to obtain the start and stop Y coordinates, we're ready to perform the actual scroll operation. Here's the function, I'll explain it below:

  1. function smoothScroll(eID) {
  2.     var startY = currentYPosition();
  3.     var stopY = elmYPosition(eID);
  4.     var distance = stopY > startY ? stopY - startY : startY - stopY;
  5.     if (distance < 100) {
  6.         scrollTo(0, stopY); return;
  7.     }
  8.     var speed = Math.round(distance / 100);
  9.     var step = Math.round(distance / 25);
  10.     var leapY = stopY > startY ? startY + step : startY - step;
  11.     var timer = 0;
  12.     if (stopY > startY) {
  13.         for ( var i=startY; i<stopY; i+=step ) {
  14.             setTimeout("window.scrollTo(0, "+leapY+")", timer * speed);
  15.             leapY += step; if (leapY > stopY) leapY = stopY; timer++;
  16.         } return;
  17.     }
  18.     for ( var i=startY; i>stopY; i-=step ) {
  19.         setTimeout("window.scrollTo(0, "+leapY+")", timer * speed);
  20.         leapY -= step; if (leapY < stopY) leapY = stopY; timer++;
  21.     }
  22. }


The first thing this function does is fetch the start and stop Y coordinates via the functions covered above. Next, it compares these two values to determine the distance between them. When setting the distance variable, an alternate control structure is used to determine which point should be subtracted from the other (depends on whether it needs to scroll up or down).

Once the start, stop and distance values are set, a check is done to see whether the destination element is less than 100 pixels away, and if so it simply jumps to it. If the destination point is further away, the function continues by dynamically setting the scroll speed (distance divided by 100), step size (distance to jump each time the visible top of page Y coordinate is changed, thus moving closer to the destination), the leapY (next coordinate to jump to), as well as the initial timer value of 0.

At this point, a check is performed to determine whether the destination point is greater than the starting point (scrolling down). If so, the if is entered and the scroll is performed otherwise the for loop at the very bottom is hit, which performs an upward scroll.

Here is how the first for loop works (scroll up). The loop starts out with i = the start Y coordinate and loops until it hits the stop Y coordinate, incrementing i the step size per iteration. Inside the loop there is a setTimeout which runs window.scrollTo with the next Y coordinate as its value (leapY). The actual timeout value is determined by multiplying the timer value by the speed. So, the timeout value for the first iteration would be 0 * speed, the second iteration would use 1 * speed and so on.

After the setTimeout, the leapY value is incremented the step size (the next Y coordinate to jump to). A check is also performed to ensure the leapY value doesn't exceed the destination coordinate.

The scroll up for loop works the exact opposite of the scroll down loop we just covered. Hopefully I've done a decent job of explaining how this function works. If you have any trouble with it, just leave a comment and I'll do my best to help out.

Finishing up


You can use smoothScroll wherever you like, it doesn't have to be an anchor. If you do use anchors, you can set them up to fall back to the href in the event JavaScript is disabled. To do this, simply use href="#anchorID" in your hyperlinks with smoothScroll('destinationAnchorID');return false as the onclick event.

That's it, enjoy!