Make Internal Links Scroll Smoothly with JavaScript
Pages: 1, 2, 3, 4, 5, 6
The complete code looks like this:
/* Smooth scrolling
Changes links that link to other parts of this page to scroll
smoothly to those links rather than jump to them directly, which
can be a little disorienting.
sil, http://www.kryogenix.org/
v1.0 2003-11-11
v1.1 2005-06-16 wrap it up in an object
*/
var ss = {
fixAllLinks: function() {
// Get a list of all links in the page
var allLinks = document.getElementsByTagName('a');
// Walk through the list
for (var i=0;i<allLinks.length;i++) {
var lnk = allLinks[i];
if ((lnk.href && lnk.href.indexOf('#') != -1) &&
( (lnk.pathname == location.pathname) ||
('/'+lnk.pathname == location.pathname) ) &&
(lnk.search == location.search)) {
// If the link is internal to the page (begins in #)
// then attach the smoothScroll function as an onclick
// event handler
ss.addEvent(lnk,'click',ss.smoothScroll);
}
}
},
smoothScroll: function(e) {
// This is an event handler; get the clicked on element,
// in a cross-browser fashion
if (window.event) {
target = window.event.srcElement;
} else if (e) {
target = e.target;
} else return;
// Make sure that the target is an element, not a text node
// within an element
if (target.nodeType == 3) {
target = target.parentNode;
}
// Paranoia; check this is an A tag
if (target.nodeName.toLowerCase() != 'a') return;
// Find the <a name> tag corresponding to this href
// First strip off the hash (first character)
anchor = target.hash.substr(1);
// Now loop all A tags until we find one with that name
var allLinks = document.getElementsByTagName('a');
var destinationLink = null;
for (var i=0;i<allLinks.length;i++) {
var lnk = allLinks[i];
if (lnk.name && (lnk.name == anchor)) {
destinationLink = lnk;
break;
}
}
// If we didn't find a destination, give up and let the browser do
// its thing
if (!destinationLink) return true;
// Find the destination's position
var destx = destinationLink.offsetLeft;
var desty = destinationLink.offsetTop;
var thisNode = destinationLink;
while (thisNode.offsetParent &&
(thisNode.offsetParent != document.body)) {
thisNode = thisNode.offsetParent;
destx += thisNode.offsetLeft;
desty += thisNode.offsetTop;
}
// Stop any current scrolling
clearInterval(ss.INTERVAL);
cypos = ss.getCurrentYPos();
ss_stepsize = parseInt((desty-cypos)/ss.STEPS);
ss.INTERVAL =
setInterval('ss.scrollWindow('+ss_stepsize+','+desty+',"'+anchor+'")',10);
// And stop the actual click happening
if (window.event) {
window.event.cancelBubble = true;
window.event.returnValue = false;
}
if (e && e.preventDefault && e.stopPropagation) {
e.preventDefault();
e.stopPropagation();
}
},
scrollWindow: function(scramount,dest,anchor) {
wascypos = ss.getCurrentYPos();
isAbove = (wascypos < dest);
window.scrollTo(0,wascypos + scramount);
iscypos = ss.getCurrentYPos();
isAboveNow = (iscypos < dest);
if ((isAbove != isAboveNow) || (wascypos == iscypos)) {
// if we've just scrolled past the destination, or
// we haven't moved from the last scroll (i.e., we're at the
// bottom of the page) then scroll exactly to the link
window.scrollTo(0,dest);
// cancel the repeating timer
clearInterval(ss.INTERVAL);
// and jump to the link directly so the URL's right
location.hash = anchor;
}
},
getCurrentYPos: function() {
if (document.body && document.body.scrollTop)
return document.body.scrollTop;
if (document.documentElement && document.documentElement.scrollTop)
return document.documentElement.scrollTop;
if (window.pageYOffset)
return window.pageYOffset;
return 0;
},
addEvent: function(elm, evType, fn, useCapture) {
// addEvent and removeEvent
// cross-browser event handling for IE5+, NS6 and Mozilla
// By Scott Andrew
if (elm.addEventListener){
elm.addEventListener(evType, fn, useCapture);
return true;
} else if (elm.attachEvent){
var r = elm.attachEvent("on"+evType, fn);
return r;
} else {
alert("Handler could not be removed");
}
}
}
ss.STEPS = 25;
ss.addEvent(window,"load",ss.fixAllLinks);
Wrapping Up
Your document's internal links will scroll to their destination, allowing your users to retain an awareness of the browser's location within the document, and how far they are from their starting point.
Stuart Langridge is an information architect for a law firm in the UK. He writes about JavaScript and DHTML, as well as pretty much anything else that catches his attention, at www.kryogenix.org.
Return to the O'Reilly Network
You must be logged in to the O'Reilly Network to post a talkback.
Showing messages 1 through 9 of 9.
-
Anchor object
2005-09-14 12:45:22 Halans [Reply | View]
-
Anchor object
2005-09-15 02:04:10 aquarius [Reply | View]
The reason I didn't use document.anchors is that it's a DOM0 thing; I try and avoid DOM0 things because they don't always exist, depending on how strict the browser is and so on. It might turn out that I'm wrong about document.anchors specifically, but document.getElementsByTagName will always work and that's the only function I need to learn, rather than document.links and document.forms and all the other DOM0 things.
-
Horizontal scrolling
2005-08-28 02:12:33 fmoser [Reply | View]
This is really improving usability of big pages.
Any idea how would it be possible to perform an horizontal scroll?
scrollTo() (http://www.mozilla.org/docs/dom/domref/dom_window_ref109.html) is used here but it can only do vertical scroll, so is it possible to code a scrollTo() which work with vertical one too?
-
Browsers should do this
2005-08-27 15:16:37 ipearx [Reply | View]
Perhaps web browsers should do this - it's a good idea, but really needs acceleration on the scrolling, and the user should be able to turn it on/off.
-
Just Eye Candy?
2005-08-27 13:08:17 joshuawait [Reply | View]
Nicely written JavaScript and well documented article.
However, I don't really see this function as an improvement of the interface. In fact, it seems confusing to me for a hyperlink to have scroll behavior. Perhaps I'm mistaken but a jump seems more intuitive for a hyperlink than the sudden whizzing of a page. -
Just Eye Candy?
2005-09-15 02:09:37 aquarius [Reply | View]
I'm quite happy with that characterisation, Joshua. I personally think that a jump is disorienting, and having page-internal links react differently (scrolling) from page-external links (a direct jump, no scrolling) is good because they can be distinguished, but there's certainly the alternative view that the current way is better, and I wouldn't argue massively with that.
-
Safari
2005-08-26 20:09:12 ipearx [Reply | View]
Hi there- just tried your demo page and it doesn't work well in Safari 2.0 on the mac (with Saft installed). The page jumps to the link when clicked (with no scrolling) and then reloads the whole page.
Regards,
Tim -
Safari
2005-08-27 03:22:32 Synchro [Reply | View]
The demo works for me in Safari, though I don't know that the scrolling is actually any less confusing than a jump. That could just be because the repeated content on the demo page confuses the eye while it's moving.
However, having added the script (downloaded directly from the link used in the demo) to my own page, I find it has no effect in either Safari or Firefox. I've stepped through it in Venkman in Firefox and it seems to be going through the motions (it starts on load, finds my anchors and attaches events), but ends up just acting like it wasn't there. No JS errors are logged, all W3C validated. Venkman seems to find it difficult to set breakpoints inside anonymous functions which doesn't help. Ideas?






Just a thought...