When a link includes a hash, like this:
<a href="#section-two">Section Two</a>
The browser window will scroll itself (instantly) into such a position where the element with the ID of “section-two” is visible. It scrolls to the minimum possible position to make that element wholly visible. This is typically a matter of scrolling the window down, but do note that if any scrollable parent container were to require horizontal scrolling to make the element visible, the browser will do that as well. I think of this as “headbutting” the browser as the element is flush with the top edge of the browser window.
This can be:
- Possibly aesthetically un-pleasing
- Possibly confusing (especially when you jump into a area with loads of other headers)
- In the case of a fixed-position stay-on-top header, hugely problematic
The fixed position header thing is the biggest threat, so let’s use that as an example and fix it.
scroll-margin-top
Update! Just use This is exactly what the scroll-margin-top
property is designed to do. As the name implies, it adds top margin to the element following a scroll event. So, if we want, say, 50px
of space between the top of the viewport and the element, we can do something like this:
But wait! If you clicked that anchor link and nothing happened, it’s likely becuse you’re on Safari 11 or older (macOS or iOS). To support those, we need to pair this with scroll-snap-margin-top
, an older version of the property:
h2 {
scroll-margin-top: 50px;
scroll-snap-margin-top: 50px; /* iOS 11 and older */
}
/* If the browser supports the property... */
@supports (scroll-margin-top: 0;) {
h2 {
scroll-margin-top: 50px;
}
}
All of the other methods covered are from the original version of this article that published in 2010.
Rock Solid (Dirty HTML) Method
Instead of focusing on the most progressive way to handle it first like we usually do, let’s look at the most cross-browser compatible possible way to get it done.
Instead of putting the ID on the header, we’ll put it on an empty span tag within the header. This won’t affect the appearance of the header at all. However, using a span for a purely behavior thing like this isn’t ideal.
<a href="#goto">Jump</a>
<!-- yadda yadda yadda -->
<h2>
<span id="goto"> </span>
Header
</h2>
Then in the CSS, we’ll suck up the span north of the actual header with negative top margin. Then we’ll push the header back down via positive bottom padding, mitigating any weird layout problems that the suck-up will cause.
h2 span {
margin-top: -300px; /* Size of fixed header */
padding-bottom: 300px;
display: block;
}
Ideally, we would just absolutely position the span on top of the header, but IE7 doesn’t play nice with that, ignoring the jumps all together. IE6 has major issues with fixed positioning, so this demo is borked in that, and let’s not go there, although I’m sure this idea basically works in it if you can shim the fixed position issue.
Fancier (Clean HTML) Method
Using the extra span is non-semantic for two reasons: (1) You are associating the link directly to an empty span, which is meaningless. (2) The span shouldn’t be in there at all. The HTML should be:
<a href="#goto">Jump</a>
<!-- yadda yadda yadda -->
<h2 id="goto">Header</h2>
Then to solve the headbutting/padding issue, we’ll use a pseudo element to do the same task that the span was doing in our dirty HTML version. We’ll give it a height, which pushes up the size of the header, then use a negative margin to yank it back up into place.
h2::before {
display: block;
content: " ";
margin-top: -285px;
height: 285px;
visibility: hidden;
pointer-events: none;
}
More from Nicolas Gallagher
I posted the original idea for this over on Forrst, and Nicolas Gallagher picked up on it and ran with it, as Nic likes to do =). He points out that the height/margin technique can be a problem if you have a background on the headers and don’t want that to expand. He prevents that by experimenting with background-clip
, using an under-border, and others. And big props to Ira McMahon for spurring the idea on Forrst.
As part of Nic’s demos, he uses :target to change the color of the header after “the jump”. This is a great reminder of that pseudo selector and a perfect use for it. Target will match when the hash tag in the URL matches the ID of an element. Quick reminder: if the URL is http://blahblahblah.com/#header-one
and there is an element like
Whatup
then this selector will match
h2:target { background: yellow; }
.
More from Patrick Strietzel
I discovered that IE7’s hashtag behavior (ignoring the
padding-top
) can be tricked by setting the display value toinline-block
.
h2 {
margin-top: -285px;
padding-top: 285px;
display: inline-block;
}
Of course, a display change like that can have consequences. inline-block
is very different from block
, so beware.
More from Kirk Gleffe
Kirk found a way to do it with just margin and a bit of transition-delay
:
More from Alex Wolfe
Alex wrote in to mention that the padding on the header might be sitting on top of the text above it. Which means it can block clicks or selecting text. You can fix with z-index
, either by wrapping text in something with a higher z-index
, or, maybe negative z-index
on the headers.
Wow – I’ve being doing web development since 1994 and somehow I never knew that you could point the hashed anchor to something other than a named anchor.
I’ve always done:
well lance,
then you actually didn’t do web development but web “design”, i think.
and yes there is a difference and yes you have to know such facts as a “developer” ;)
please don’t take it personally, but it’s just like that.
Actually, I do “development” (ie. data and algorithms) not design – hence my middling familiarity with the finer points of html.
You should also be able to pull this off without the need for any pseudo elements. I believe this should do the trick:
h2 {margin-top: -285px; padding-top: 285px;}
Not able to try it in IE7 & below atm. As long as you avoid having backgrounds/bg images on the headings, you should be ok.
@Lance
Holy, Cow! I thought the exact same thing too! So I was a bit confused when reading this. I’m itching to go back to some old sites and redo this part now…
For those of you that weren’t away that anchor tags can be linked to the id of any element may want to read http://www.w3.org/TR/html401/struct/links.html#h-12.2.3.
And when it comes to making these types of links more visually appealling, I like to use Ariel Flesler’s jQuery.ScrollTo plugin http://flesler.blogspot.com/2007/10/jqueryscrollto.html. It’s been a round a long time and well done.
That’s what I use. Love the plugin b/c it gives user a visual clue about where they’re going; that they’re still on the same page.
I’ve always done this:
<a name="someheading"></a>
<h2>Some Heading</h2>
I don’t think it’s any more un-semantic than your typical (non-contextual) hyperlink, and it completely sidesteps the css issues.
In the current HTML5 spec, using the ‘name’ attribute on an anchor is invalid.
Furthermore, it doesn’t completely sidestep the issue at all. You still have to position the anchor somewhere above the heading without affecting the visual flow of the document. That means using attribute selectors (not supported by older versions of IE) or adding a class/id to the anchors.
sorry, I meant to use id instead of name. And yes, there would be styling involved to give it height. What I wanted to “sidestep” was all the pulling-up-and-pushing-down.
However, after looking at Chris’ demos, I think I missed the point of these exercises (I only looked at your demos, at first, and Chris’ has a giant red banner that really illustrates the complications these methods are trying to address).
Typically, I have hash links where the webpage’s layout wouldn’t present such problems. An extra 2em of empty space doesn’t bother me (typically, a hash link would be between major sections, so extra space would be appropriate anyway). However, if I actually need to work around another element (like that giant banner, or a photo or comment box or something), then it wouldn’t be an appropriate solution.
Wow, this is interesting. I just recently implemented this exact concept (using “dirty HTML” into this site which I coded for a designer friend.
You’ll see it has the fixed-position header and it uses the jQuery scrollTo plugin. I had it working exactly the same in pretty much every browser with no extra HTML elements — but IE7 kept collapsing the margin when one of the links was clicked. I’ve seen this behaviour before with IE7, but nothing seemed to fix it, even giving the element layout and whatnot. I even tried to reapply the margin settings via JavaScript. (For IE6 the header isn’t fixed, it just scrolls normally)
So I had to resort to adding the extra element to avoid using negative margins. I think the pseudo-element solution is a great option, but I’m wondering if the same problem would occur with the margins in IE7. Would be worth looking into.
Almost forgot — the :before pseudo-element is not supported in IE7, so the last option wouldn’t work if IE7 support is needed.
Looking at this again, I realized that you’re using the span element in a different way than I first understood.
Here is a solution with clean HTML, and works in every browser except IE6:
http://www.impressivewebs.com/bump.html
The key changes are IDs on the headings, which then get the margin/padding trick. And there’s a negative margin on the nav section and some top padding on the page wrap, to straighten things out.
CSS-wise, it’s not the cleanest solution, but for cross-browser compatibility, I think it’s acceptable, and the markup is clean.
Still not sure why my live-client version was not behaving in IE7, because this is pretty much what I did, and this works in IE7.
Hi Louis, in my article (linked to in Chris’ article) I cover that technique. The problem comes when you want to set a background colour or image on the target element, and if you want to use padding-top too. The solution is to use border-top and background-clip (Method E).
Yes, thanks for pointing that out. I didn’t actually look at your pages until now. But I think the need for backgrounds would be unlikely, and can be dealt with accordingly.
The only version of IE that supports background-clip is IE9 Beta. So, the only real-world, cross-browser solution to this problem is Chris’s “dirty HTML” method or the similar one I’ve demoed above.
But it is great to know what will be possible when older versions of IE fall out of use. Just not very practical until then.
Making a slight addition to my previous post. This seems like a possible solution:
h2 {margin-top: -285px; border-top: 285px solid #same-as-background; background: url(“bg.jpg”);}
No pseudo element, no clipping involved (unless your heading is on top of a container with a background image).
Yep Anders, of course, but then if you have an repeating image as part of the parent element’s background the border color method won’t work.
It is Good.
I have always go with the below to target location.
<a name=”top”>Anchor Position</a>
<a href=”#top” >Top</a>
-vara
how about this
<a href="#section">Jump</a>
<h2 id="section" style="padding-top:50px">Header</h2>
Nice examples of hash tag links for on-page scrolling. LT
I have a fixed position header 70px high. By playing with the values I was able to get this to work in Chrome. Is there a fix that works in IE 10? Here is my CSS snipet:
h2
{
font-family:Georgia, “Times New Roman”, Times, serif;
color:#306;
}
h2:before {
content: ” “;
margin-top: -175px;
height: 175px;
visibility: hidden;
display: block;
display: inline-block;
}
What about if the div you are trying to scroll to is nested in multiple other divs? For example using bootstrap with row and column classes and trying to scroll to a specific column of content.
I’m having the same problem with dreamweaver tabbed panels.
once again, you helped solve my problem quickly! thanks a ton.
If have a background on my
<div id="goto" class="section"></div>
which fix can I use to avoid a huge space before the content of this div start (in a one page layout)?I was thinking the issue to solve needs some js. But you come up cleaner css h2:before method. Thank you so much for sharing.
How would I prevent The URL in the browser updating with the hashtag?
I know there’s a way to do it with JavaScript or jQuery but I can’t find it. Thanks
Might be usefull to add this to the Rock Solid (Dirty HTML) Method :
h2 span {
margin-top: -300px; /* Size of fixed header */
padding-bottom: 300px;
display: block;
font-size:1px;
line-height:1px;
}
I’m wondering if there’s a way to do this without having to hard code the pixels for height and margin-top? I used this in a project of mine and when you change the size of the browser window it doesn’t work properly.
Imagine I’m looking at one page in my site, in which I’m scrolled down to some location,
and I click a link on this page that makes me jump to a page which is an exact copy of the first page.
Now I can land on certain points on that copy page, using the code above, thanks for sharing,
but here’s the mind-bender: how do I jump to the EXACT SAME LOCATION where I have scrolled to now.
For example, imagine I have scrolled 120 pixels down the page, how do I jump to a copy that is exactly 120 pixels down the page.
It seems 2 instructions are needed, “#1 – notice how many pixels the person has scrolled to currently”, and “#2 – jump to a copy of that page AT THE EXACT SAME scroll spot.”
I would appreciate help on this, and will gratefully snail mail $20 to whoever can post the working code first.
Thanks again.
(PS – in case you’re wondering, there IS a rational reason to wanting to jump to a copy of the current page. Imagine you have a non-playing video on the first page. When someone clicks on that, we jump to a copy page where that same video is sitting yet on this copy page the video starts automatically. Then from there, if one scrolls down a little further, there is a second video on that copy page, and if we click on that we jump to another copy of the same page, in which the first video is not playing while the second video IS playing.)
That’s why I use these copy pages, to make auto-play work WHILE telling all other videos to stop playing.
So, here too, If someone has a better working code idea, that can accomplish the goal of “clicking on a video starts the video WHILE telling any other videos playing to STOP playing.” This would be the cleaner solution.
So for either of those two requests above, I would appreciate help, and will gratefully snail mail $20 to whoever can post the working code first, for either request.
Thank you. :-)
This demo is great, thank you for your time in putting this together. Would there be an easy way to modify it to adjust for a menu bar at the top of the screen? When I click to the “next” anchor, my section content title is obfuscated by my menu bar.
Thanks for any ideas on this.,
David
I just wanted to say a big THANK YOU! This cross-browser problem had been plaguing me for days – your concise, clean method was perfect.