Watch
Watch Scroll Linked Navigation screencast (Alternative flash version)
QuickTime version is approximately 50Mb, flash version is streaming.
View the demo used in the screencast
The Problem
What you need to do is hook in to the user scrolling the web page, and when a particular element comes in to view, if there’s an associated navigation item – make it selected. That’s the trick, the hard part is working out exactly which element is now in view.
Fortuantly there’s a jQuery plugin for that particular job that makes it really easy to select only those elements that are in the viewport (as well as outside the viewport): viewport selectors plugin.
It’s not a new plugin by any means, but you need to have a specific reason to want to use it – and it fits this job perfectly.
Solving using the viewport selector plugin
Using the sample page I’ve mocked for this example, if you test a jQuery selector using your favourite debugging tool we can work out which section of the page the user is looking at. For this example, I’m going to say the navigation is linked to the topmost h1 in view (from within the console):
$('h1:in-viewport').length
This .length test should give me one or more. Using the h1 element, I’ll navigate up the DOM to find the parent element and use it’s id to update the navigation as appropriate.
Using the id from our container element, we’ll look for a navigation link that contains a hash that matches the id. This is a common pattern that I’ve used several times before, and will look something like this where ourID is the id we got using the viewport selector:
$('nav a[hash=#ourID]')
Don’t worry about that bit just yet, once you see it all working you should get a better idea of how it all fits together.
Before we crack on with the jQuery code required to make this all work, make sure you’ve included both jQuery then the jQuery viewport selectors plugin:
<script src="jquery-1.4.2.js"></script>
<script src="jquery.viewport.js"></script>
Now, we’re ready to rock.
The Solution
We need to satisfy the following steps to make this simple effect work:
- Listen (
bind) for when the user scrolls the window - Find the topmost
h1element that matches the:in-viewportselector - Use that
h1element to find theidfrom the container, and use that to find the related navigation link - If the navigation link isn’t already selected, the remove the selected class from all the navigation links, and finally -
- Add the selected class to the found navigation link
Using this breakdown, let’s start adding the code in. First listen for when the user scrolls:
$(window).scroll(function () {
});
We need to listen on the window because it’s the whole window the user can scroll. If you wanted to localise this technique to a specific element with overflow, you would use something like $('#foo').scroll(fn).
Our h1s are inside the #content > section selector, but I want to allow for more than h1 in the section, need to find all that are in the viewport, then narrow to the first:
$(window).scroll(function () {
var inview = $('#content > section > h1:in-viewport:first');
});
Now the inview variable will have a jQuery object. This isn’t what we want, we want the id and we need it to match the hash attribute on a link. So if the id is ‘first’, the hash should be ‘#first’. So let’s get the id from the parent section element and get the id attribute and finally prepend a # symbol to create the hash:
$(window).scroll(function () {
var inview = '#' + $('#content > section > h1:in-viewport:first').parent().attr('id');
});
Normally I wouldn’t use the attr('id') method to get the element id, I would do something like .parent()[0].id, but because we’re using the :in-viewport selector, there’s a good chance that the selector will fail to match anything if we scroll too far, so using jQuery’s attr is a safe way of getting some value without our code breaking.
Next we’ll use this hash to find the navigation link:
$(window).scroll(function () {
var inview = '#' + $('#content > section > h1:in-viewport:first').parent().attr('id'),
$link = $('nav a').filter('[hash=' + inview + ']');
});
Make sure you try out this newly added line of code in your debugger – swapping the variable inview for a string like ‘#first’, etc. It’ll show you which element it’s selecting.
There’s two tests we need to make before continuing:
- Did we actually match anything, using
length - Only proceed if the link isn’t already selected
$(window).scroll(function () {
var inview = '#' + $('#content > section > h1:in-viewport:first').parent().attr('id'),
$link = $('nav a').filter('[hash=' + inview + ']');
if ($link.length && !$link.is('.selected')) {
}
});
Finally we remove the selected class from the existing navigation and add it to the correctly found $link navigation:
$(window).scroll(function () {
var inview = '#' + $('#content > section > h1:in-viewport:first').parent().attr('id'),
$link = $('nav a').filter('[hash=' + inview + ']');
if ($link.length && !$link.is('.selected')) {
$('nav a').removeClass('selected');
$link.addClass('selected');
}
});
You should follow me on Twitter here I tweet about jQuery amongst the usual tweet-splurges!

Play QuickTime version
Play Flash version