CSS or Javascript? Internal Link Targets and Fixed Headers
How do you keep target links from getting hidden under fixed headers?
Do you want to use CSS or Javascript when navigating to internal page targets on pages with fixed headers? It can be tricky getting an internal page target to position itself properly at the top of the visible area in a browser window when there are fixed header elements at the top of the page. Here are some thoughts about how to approach and resolve the issues that can come up.
Using Internal Page Targets
A convenient way to navigate within the content of a long HTML document is to use the id attributes of elements on the page as link targets. By appending a # sign and the id of one of these elements to the URL in the href of a link (eg: <a href="page.html#target">Page Target</a> for an element with an id of "target" on page.html) you can make the browser scroll directly to that position on the page when the page is loaded.
The body for a page might look like this:
<nav class="fixed-element"> <ul> <li><a href="#some_element">Element One</a></li> <li><a href="#some_other_element">Element Two</a></li> <li><a href="#a_third_element">Element Three</a></li> </ul> </nav> <section class="content"> <h1>This is a long page</h1> <h2 id="some_element">Some Element</h2> <p>This is a paragraph of text associated with some element.</p> <p>(Imagine it's a long one that would force lower content to scroll off the screen.)</p> <h2 id="some_other_element">Some Other Element</h2> <p>This is a paragraph of text associated with some other element.</p> <p>(Imagine it's also a long one.)</p> <h2 id="a_third_element">A Third Element</h2> <p>This is a paragraph of text associated with a third element.</p> <p>(Imagine it's also a long one. You get the idea.)</p> </section>
The CSS for this page might look like this
.fixed-element {
width: 100%;
height: 80px;
position: fixed;
top: 0;
background: #900;
}
.fixed-element ul {
overflow: auto;
}
.fixed-element li {
list-style-type: none;
margin-right: 10px;
float: left;
}
.fixed-element a {
display: block;
color: #fff;
}
.content {
padding-top: 80px;
}
In this case your page has a toolbar at the top, or some other fixed header. These are usually positioned with fixed or absolute positioning at the top of the page, so the rest of the page scrolls underneath. The page navigation is using just a target id for an element on the page in the href of the links to refresh the scroll position.
When you link directly to an internal target, the page will scroll the target element all the way to the top of the window, without regard to the positioning of the header, and the content you are trying to target will be hidden under your fixed header. The problem is getting the browser to scroll to a position that allows your target content to be viewed directly below the fixed header.
A Javascript approach
One way to approach this is with Javascript. There are native Javascript ways to get the offset of the target elements from the top of the window, and to set the scroll position. However, to deal with cross-browser issues, it is generally more practical to use a popular library such as JQuery.
To do this dynamically, you need to determine on page load if the URL has a target identifier appended. When there is one, it is available in Javascript's location object, in the hash property. By testing to see if location.hash exists, and then using it to create a JQuery object, you can get its offset().top value. This value, minus the height of the fixed element, can be used to adjust the scrollTop() of the JQuery object for the body element.
The Javascript to make this work might look like this
$(function() {
if (location.hash) {
var target_position = $(location.hash).offset().top;
$('body').scrollTop(target_position - 80);
}
});
This will work because JQuery's ready function will be called every time the page is refreshed. When a target link is clicked, the effect is the same as refreshing the page, which again invokes the JQuery.
A CSS approach
An alternative is to add a class to the page targets, and using some CSS to trick the browser into believing that the top of the page is actually offset from the top of the target element by the appropriate distance.
The additional CSS for this page might look like this
.link-target {
padding-top: 80px;
margin-top: -80px;
}
The revised content section might look like this:
<section class="content"> <h1>This is a long page</h1> <h2 id="some_element" class="link-target">Some Element</h2> <p>This is a paragraph of text associated with some element.</p> <p>(Imagine it's a long one.)</p> <h2 id="some_other_element" class="link-target">Some Other Element</h2> <p>This is a paragraph of text associated with some other element</p> <p>(Imagine it's also a long one.)</p> </section>
By adding extra padding to the top of the element you want to have appear at the top of the page, you can trick the browser into scrolling to a position that is high enough on the page to display your content below the fixed elements at the top. Using a negative top margin will prevent this extra padding from changing the existing layout of the elements on the page, so you don't create additional white space above the target elements. Of course, you may need to adjust the values for the padding and negative margin depending on the styles you already have.
It's your choice how you would prefer to manage the issue. The Javascript approach doesn't require any addition markup in your pages, but it won't work if Javascript is disabled. The CSS approach requires each target to have a class, and may involve a little adjustment to accommodate your existing layout, but it doesn't require running scripts to achieve the desired visual effect.

