scroll your internal links smoothly

December 04 2005, 14:03:00

Notice: This article now use obsolete code, as it has been replaced by mootools. Go get it now.

A “quick post” on how to scroll your internal links smoothly with moo.fx, using normal anchors.

1. The Effect

So, I’ve created a basic effect, to smoothly scroll from an object’s position to another.

fx.Scroll = Class.create();
fx.Scroll.prototype = Object.extend(new fx.Base(), {
    initialize: function(options) {
        this.setOptions(options);
    },

    scrollTo: function(el){
        var desty = Position.cumulativeOffset($(el));
        var dest = desty[1];
        var client = window.innerHeight || document.documentElement.clientHeight;
        var full = document.documentElement.scrollHeight;
        var top = window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop;
        if (dest+client > full) this.custom(top, dest - client + (full-dest));
        else this.custom(top, dest);
    },

    increase: function(){
        window.scrollTo(0, this.now);
    }
});

note: I’ve taken advantage of the prototype function cumulativeOffset here. If you want to test this effect, and you’re using prototype.lite from moo.fx download, make sure you add this at the end:

var Position = {
  cumulativeOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
    } while (element);
    return [valueL, valueT];
  }
}

This does not apply if you’re using the full prototype.js.

2. Gettin all your links

Now I’ve got a nice scroll effect. But it’s pretty useless, I want to use html anchors, or my users without javascript will not be taken to the part of the page I want. Plus, I don’t want to change anything on the html. Just use existing anchors.

This is the “class” I’ve came up with:

var ScrollLinks = {
    currentHash: false,
    start: function(){
        this.scroll = new fx.Scroll({duration: 800, onComplete: function(){ScrollLinks.end();}});
        this.allinks = document.getElementsByTagName('a');
        for (i=0; i<this.allinks.length; i++){
            var lnk = this.allinks[i];
            if ((lnk.href && lnk.href.indexOf('#') != -1) && ( (lnk.pathname == location.pathname) || ('/'+lnk.pathname == location.pathname) ) && (lnk.search == location.search)) {
                lnk.onclick = function(){
                    ScrollLinks.scroll.clearTimer();
                    this.initialHref = this.href;
                    this.initialHash = this.hash;
                    this.href = "javascript:void(0)";
                    setTimeout(function(){this.href = this.initialHref;}.bind(this), 200);
                    ScrollLinks.click(this);
                }
            }
        }
    },

    click: function(link){
        this.currentHash = link.initialHash.substr(1);
        if (this.currentHash) {
            for (j=0; j<this.allinks.length; j++){
                if (this.allinks[j].id == this.currentHash){
                    if (!window.opera) this.scroll.scrollTo(this.allinks[j]);
                    else this.scroll.scrollTo(this.allinks[j].parentNode);
                    break;
                }
            }
        }
    },

    end: function(){
        window.location.href = "#"+this.currentHash;
        this.currentHash = false;
    }
}

It takes care of everyting: creates the effect, and scroll the page in a smooth way when you click on an internal link.

Also the usage is pretty basic:

window.onload = function(){
    ScrollLinks.start();
}

Opera has some troubles finding the position of an anchor, I guess that’s because it’s empty, but I’m not sure. So in opera the window scrolls to the anchor’s parentNode. Also, Safari seems to behave strangely when I manually set window.location.href.

Do let me know if you find a better solution. When these two nasty things are fixed, this will probably be a part of moo.fx.pack.

P.s. To see this effect in action just click back to top or add a comment.