Question

If I have a non-scrolling header in an HTML page, fixed to the top, having a defined height:

Is there a way to use the URL anchor (the #fragment part) to have the browser scroll to a certain point in the page, but still respect the height of the fixed element without the help of JavaScript?

http://foo.com/#bar
WRONG (but the common behavior):         CORRECT:
+---------------------------------+      +---------------------------------+
| BAR///////////////////// header |      | //////////////////////// header |
+---------------------------------+      +---------------------------------+
| Here is the rest of the Text    |      | BAR                             |
| ...                             |      |                                 |
| ...                             |      | Here is the rest of the Text    |
| ...                             |      | ...                             |
+---------------------------------+      +---------------------------------+
Was it helpful?

Solution

I had the same problem. I solved it by adding a class to the anchor element with the topbar height as the padding-top value.

<h1><a class="anchor" name="barlink">Bar</a></h1>

And then simply the css:

.anchor { padding-top: 90px; }

OTHER TIPS

If you can’t or don’t want to set a new class, add a fixed-height ::before pseudo-element to the :target pseudo-class in CSS:

:target::before {
  content: "";
  display: block;
  height: 60px; /* fixed header height*/
  margin: -60px 0 0; /* negative fixed header height */
}

Or scroll the page relative to :target with jQuery:

var offset = $(':target').offset();
var scrollto = offset.top - 60; // minus fixed header height
$('html, body').animate({scrollTop:scrollto}, 0);

I use this approach:

/* add class="jumptarget" to all targets. */

.jumptarget::before {
  content:"";
  display:block;
  height:50px; /* fixed header height*/
  margin:-50px 0 0; /* negative fixed header height */
}

It adds an invisible element before each target. It works IE8+.

Here are more solutions: http://nicolasgallagher.com/jump-links-and-viewport-positioning/

Official Bootstrap Adopted Answer:

*[id]:before { 
  display: block; 
  content: " "; 
  margin-top: -75px; // Set the Appropriate Height
  height: 75px; // Set the Appropriate Height
  visibility: hidden; 
}

Credits

Merge

The best way that I found to handle this issue is (replace 65px with your fixed element height):

div:target {
  padding-top: 65px; 
  margin-top: -65px;
}

If you do not like to use the target selector you can also do it in this way:

.my-target {
    padding-top: 65px;
    margin-top: -65px;
}

Note: this example will not work if the target element have a backgound color that differant from his parent. for example:

<div style="background-color:red;height:100px;"></div>
<div class="my-target" style="background-color:green;height:100px;"></div>

in this case the green color of my-target element will overwrite his parent red element in 65px. I did not find any pure CSS solution to handle this issue but if you do not have another background color this solution is the best.

While some of the proposed solutions work for fragment links (= hash links) within the same page (like a menu link that scrolls down), I found that none of them worked in current Chrome when you want to use fragment links coming in from other pages.

So calling www.mydomain.com/page.html#foo from scratch will NOT offset your target in current Chrome with any of the given CSS solutions or JS solutions.

There is also a jQuery bug report describing some details of the problem.

SOLUTION

The only option I found so far that really works in Chrome is JavaScript that is not called onDomReady but with a delay.

// set timeout onDomReady
$(function() {
    setTimeout(delayedFragmentTargetOffset, 500);
});

// add scroll offset to fragment target (if there is one)
function delayedFragmentTargetOffset(){
    var offset = $(':target').offset();
    if(offset){
        var scrollto = offset.top - 95; // minus fixed header height
        $('html, body').animate({scrollTop:scrollto}, 0);
    }
}

SUMMARY

Without a JS delay solutions will probably work in Firefox, IE, Safari, but not in Chrome.

For Chrome/Safari/Firefox you could add a display: block and use a negative margin to compensate the offset, like:

a[name] {
    display: block;
    padding-top: 90px;
    margin-top: -90px;
}

See example http://codepen.io/swed/pen/RrZBJo

You can do this with jQuery:

var offset = $('.target').offset();
var scrollto = offset.top - 50; // fixed_top_bar_height = 50px
$('html, body').animate({scrollTop:scrollto}, 0);

You could try this:

<style>
h1:target { padding-top: 50px; }
</style>

<a href="#bar">Go to bar</a>

<h1 id="bar">Bar</h1>

Set the top padding value to the actual height of your header. This will introduce a slight extra gap at the top of your header, but it will only be visible when the user jumps to the anchor and then scrolls up. I've made up that solution for my site right now, but it only shows a small fixed bar at the top of the page, nothing too high.

It works for me:

HTML LINK to Anchor:

<a href="#security">SECURITY</a>

HTML Anchor:

<a name="security" class="anchor"></a>

CSS :

.anchor::before {
    content: "";
    display: block;
    margin-top: -50px;
    position: absolute;
}

I've got it working easily with CSS and HTML, using the "anchor:before" method mentioned above. I think it works the best, because it doesn't create massive padding between your divs.

.anchor:before {
  content:"";
  display:block;
  height:60px; /* fixed header height*/
  margin:-60px 0 0; /* negative fixed header height */
}

It doesn't seem to work for the first div on the page, but you can counter that by adding padding to that first div.

#anchor-one{padding-top: 60px;}

Here's a working fiddle: http://jsfiddle.net/FRpHE/24/

I'm using @Jpsy's answer, but for performance reasons I'm only setting the timer if the hash is present in the URL.

$(function() {
      // Only set the timer if you have a hash
      if(window.location.hash) {
        setTimeout(delayedFragmentTargetOffset, 500);
      }
  });

function delayedFragmentTargetOffset(){
      var offset = $(':target').offset();
      if(offset){
          var scrollto = offset.top - 80; // minus fixed header height
          $('html, body').animate({scrollTop:scrollto}, 0);
          $(':target').highlight();
      }
  };
html {
  scroll-padding-top: 70px; /* height of sticky header */
}

from: https://css-tricks.com/fixed-headers-on-page-links-and-overlapping-content-oh-my/

It feels somewhat hacky to my purist mind but as a css-only solution you can add padding to the active anchored element using the :target selector:

html, body {height:100%; min-height:100%; margin:0;}
body {min-height:200%;}
header {display:inline-block; position:fixed; font-size:1.5em; height:100px; top:0; left:0; right:0; line-height:100px; background:black; text-align:center;}
header a {color:#fff;}
section {padding:30px; margin:20px;}
section:first-of-type, section:target {padding-top:130px;}
<header><a href="#one">#One</a> <a href="#two">#two</a> <a href="#three">#three</a></header>
<section id="one"><h1>One</h1>Aenean lacinia bibendum nulla sed consectetur. Nullam id dolor id nibh ultricies vehicula ut id elit. Integer posuere erat a ante venenatis dapibus posuere velit aliquet.</section>
<section id="two"><h1>Two</h1>Aenean lacinia bibendum nulla sed consectetur. Nullam id dolor id nibh ultricies vehicula ut id elit. Integer posuere erat a ante venenatis dapibus posuere velit aliquet.</section>
<section id="three"><h1>Three</h1>Aenean lacinia bibendum nulla sed consectetur. Nullam id dolor id nibh ultricies vehicula ut id elit. Integer posuere erat a ante venenatis dapibus posuere velit aliquet.</section>

I found I had to use both MutttenXd's and Badabam's CSS solutions together, as the first did not work in Chrome and the second did not work in Firefox:

a.anchor { 
  padding-top: 90px;
}

a.anchor:before { 
  display: block;
  content: "";
  height: 90px;
  margin-top: -90px;
}

<a class="anchor" name="shipping"></a><h2>Shipping (United States)</h2>
...

The way that I find being the cleanest is the following one :

  #bar::before {
    display: block;
    content: " ";
    margin-top: -150px;
    height: 150px;
    visibility: hidden;
    pointer-events: none;
  }

I had a lot of trouble with many of the answers here and elsewhere as my bookmarked anchors were section headers in an FAQ page, so offsetting the header didn't help as the rest of the content would just stay where it was. So I thought I'd post.

What I ended up doing was a composite of a few solutions:

  1. The CSS:

    .bookmark {
        margin-top:-120px;
        padding-bottom:120px; 
        display:block;
    }
    

Where "120px" is your fixed header height (maybe plus some margin).

  1. The bookmark link HTML:

    <a href="#01">What is your FAQ question again?</a>
    
  2. The bookmarked content HTML:

    <span class="bookmark" id="01"></span>
    <h3>What is your FAQ question again?</h3>
    <p>Some FAQ text, followed by ...</p>
    <p>... some more FAQ text, etc ...</p>
    

The good thing about this solution is that the span element is not only hidden, it is essentially collapsed and doesn't pad out your content.

I can't take much credit for this solution as it comes from a swag of different resources, but it worked best for me in my situation.

You can see the actual result here.

As CSS Scroll Snap spec comes into game, it is easily possible with scroll-margin-top property. Currently runs on Chrome and Opera (April 2019). Also Safari 11+ should support this, but I was unable to run it on Safari 11. Probably we have to wait for guys to fix it overthere.

Codepen example

body {
  padding: 0;
  margin: 0;
}

h1,
p {
  max-width: 40rem;
  margin-left: auto;
  margin-right: auto;
}
h1 {
  scroll-margin-top: 6rem; /* One line solution. :-) */
}
.header {
  position: sticky;
  top: 0;
  background-color: red;
  text-align: center;
  padding: 1rem;
}
.header .scroll {
  display: block;
  color: white;
  margin-bottom: 0.5rem;
}
.header .browsers {
  color: black;
  font-size: 0.8em;
}
<header class="header">
  <a class="scroll" href="#heading">Scroll to heading</a>
  <span class="browsers" >Chrome 69+, Opera 56+ and Safari 11+ only</span>
</header>
<p>
  Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
</p>
<h1 id="heading">What is Lorem Ipsum?</h1>
<p>
  Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
</p>
<p>
  

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent pulvinar eleifend dolor, in cursus augue interdum quis. Morbi volutpat pulvinar nisl et condimentum. Quisque elit lacus, egestas non ante sit amet, hendrerit commodo dui. Nunc ac sagittis dolor. Proin iaculis ante non est pharetra, et ullamcorper nisl accumsan. Aenean quis leo vel sapien eleifend aliquam. Pellentesque finibus dui ex, blandit tristique risus vestibulum vitae. Nam a quam ac turpis porta eleifend. Sed at hendrerit risus, ac efficitur ante. Aenean pretium justo feugiat condimentum consectetur. Etiam mattis urna id porta hendrerit.
</p>
<p>
Mauris venenatis quam sed egestas auctor. Fusce lacus eros, condimentum nec quam vel, malesuada gravida libero. Praesent vel sollicitudin justo. Donec mattis nisl id mauris scelerisque, quis placerat lectus scelerisque. Ut id justo a magna mattis luctus. Suspendisse massa est, pretium vel suscipit sit amet, iaculis at mi. Aenean vulputate ipsum non consectetur sodales. Proin aliquet erat nec mi eleifend, eu dapibus enim ultrices. Sed fringilla tortor ac rhoncus consectetur. Aliquam aliquam orci ultrices tortor bibendum facilisis.
</p>
<p>
Donec ultrices diam quam, non tincidunt purus scelerisque aliquam. Nam pretium interdum lacinia. Donec sit amet diam odio. Donec eleifend nibh ut arcu dictum, in vulputate magna malesuada. Nam id dignissim tortor. Suspendisse commodo, nunc sit amet blandit laoreet, turpis nibh rhoncus mi, et finibus nisi diam sed erat. Vivamus diam arcu, placerat in ultrices eu, porta ut tellus. Aliquam vel nisi nisi.
</p>
<p>
Integer ornare finibus sem, eget vulputate lacus ultrices ac. Vivamus aliquam arcu sit amet urna facilisis consectetur. Sed molestie dolor et tortor elementum, nec bibendum tortor cursus. Nulla ipsum nulla, luctus nec fringilla id, sagittis et sem. Etiam at dolor in libero pharetra consequat. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Suspendisse quis turpis non diam mattis varius. Praesent at gravida mi. Etiam suscipit blandit dolor, nec convallis lectus mattis vitae. Mauris placerat erat ipsum, vitae interdum mauris consequat quis. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</p>
<p>
Nunc efficitur scelerisque elit. Integer ac massa ipsum. Cras volutpat nulla purus, quis molestie dolor iaculis eget. Maecenas ut ex nulla. Pellentesque sem augue, ornare ut arcu eu, porttitor consectetur orci. Aenean iaculis blandit quam, in efficitur justo sodales auctor. Vivamus dignissim pellentesque risus eget consequat. Pellentesque sit amet nisi in urna convallis egestas vitae nec mauris. 
</p>

A minimally intrusive approach using jQuery:

Link:

<a href="#my-anchor-1" class="anchor-link">Go To Anchor 1</a>

Content:

<h3 id="my-anchor-1">Here is Anchor 1</a>

Script:

$(".anchor-link").click(function() {
    var headerHeight = 120;
    $('html, body').stop(true, true).animate({
        scrollTop: $(this.hash).offset().top - headerHeight
    }, 750);
    return false;
});

By assigning the anchor-link class to the links, the behaviour of other links (like accordion or tab controls) are not affected.

The question doesn't want javascript but the other more popular question is closed because of this one and I couldn't answer there.

I needed something that works for inbound links, links on page, AND that can be targeted by JS so the page can respond to changes in the header height

HTML

<ul>
  <li><a href="#ft_who">Who?</a></li>
  <li><a href="#ft_what">What?</a></li>
  <li><a href="#ft_when">When?</a></li>
</ul>
...
<h2 id="ft_who" class="fragment-target">Who?</h2> 
...
<a href="#">Can I be clicked?</a>
<h2 id="ft_what" class="fragment-target">What?</h2>
...
<h2 id="ft_when" class="fragment-target">When?</h2> 

CSS

.fragment-target {
    display: block;
    margin-top: -HEADER_HEIGHTpx;
    padding-top: HEADER_HEIGHTpx;
    z-index: -1;
}

The z-index: -1 allows links in the 'padding area' above a fragment-target to still be clickable, as commented by @MuttenXd on his answer

I haven't found an issue yet in IE 11, Edge 15+, Chrome 38+, FF 52+, or Safari 9.1+

I wasn't having any luck with the answer listed above and ended up using this solution which worked perfectly...

Create a blank span where you want to set your anchor.

<span class="anchor" id="section1"></span>
<div class="section"></div>

And apply the following class:

.anchor {
  display: block;
  height: 115px;       /* same height as header */
  margin-top: -115px;  /* same height as header */
  visibility: hidden;
}

This solution will work even if the sections have different colored backgrounds! I found the solution at this link.

<div style="position:relative; top:-45px;">
    <a name="fragment"> </a>
</div>

This code should do the trick. Swap out 45px for the height of your header bar.

EDIT: If using jQuery is an option, I've also been successful using jQuery.localScroll with an offset value set. The offset option is a part of jQuery.scrollTo, which jQuery.localScroll is built upon. A demo is available here: http://demos.flesler.com/jquery/scrollTo/ (second window, under 'offset')

Here is a complete jquery solution that will work in IE:

Suppose the navigation bar elements are something like this:

<ul>
    <li><a class="navigation" href="/#contact-us">Contact us</a></li>
    <li><a class="navigation" href="/#about-us">About us</a></li>
</ul>

You can use the following jquery snippet to offset the scroll:

$(function() {
    $("a.navigation").click(function(event) {
        event.preventDefault();
        offSetScroll($(this));
    });
    offSetScrollFromLocation(window.location.href.toLowerCase());
});

function offSetScroll(anchor) {
    var href = anchor.attr("href");
    offSetScrollFromLocation(href);
}

function offSetScrollFromLocation(href) {
    //Change this according to the height of the navigation bar
    var fromTop = 250;
    if(href.indexOf("#")<=0)
        return;
    var hash=href.substring(href.indexOf("#"));
    var targetOffset = $(hash).offset().top-fromTop;
    $('html, body').animate({scrollTop: targetOffset}, 400, function(e) {

    });
}

Implemented using :before worked great until we realized that the pseudo element was actually covering and blocking pointer events that sat within the pseudo element's area. Using something like pointer-events: none on the :before or even directly on the anchor had no affect.

What we ended up doing was making the anchor's positioning absolute and then adjusting it's position to be the offset/height of the fixed area.

Offset Anchor without Blocking Pointer Events

.section-marker {

    position: absolute;
    top: -300px;
}

The value with this is that we're not blocking any elements that might fall within those 300px. The downside is that grabbing that element's position from Javascript needs to take into account that offset so any logic there had to be adjusted.

This is how I got it to finally go to the proper place when you click on the navigation. I added an event handler for the navigation clicks. Then you can just use "scrollBy" to move up on the offset.

var offset = 90;

 $('.navbar li a').click(function(event) {
    event.preventDefault();
    $($(this).attr('href'))[0].scrollIntoView();
    scrollBy(0, -offset);
 });

I created a div with a few line breaks and gave that the id, I then put the code I wanted to show underneath. The link would then take you to the space above the image and the header would no longer be in the way:

<a href="#image">Image</a>
<div id="image"><br><br></div>
<img src="Image.png">

Of course, you can change the number of line breaks to suit your needs. This worked perfectly for me, not sure if there are any problems though, I am still learning HTML.

Used this script

$(document).on('click', 'a[href^="#"]', function (event) {
    event.preventDefault();

    $('html, body').animate({
        scrollTop: $($.attr(this, 'href')).offset().top -140
    }, 1000);
});
// handle hashes when page loads
// <http://stackoverflow.com/a/29853395>
function adjustAnchor() {
  const $anchor = $(':target');
  const fixedElementHeight = $('.navbar-fixed-top').outerHeight();
  if ($anchor.length > 0)
    window.scrollTo(0, $anchor.offset().top - fixedElementHeight);
}
$(window).on('hashchange load', adjustAnchor);
$('body').on('click', "a[href^='#']", function (ev) {
  if (window.location.hash === $(this).attr('href')) {
    ev.preventDefault();
    adjustAnchor();
  }
});

I use this method because for some reason, none of the other solutions proposed actually worked for me. I promise I tried.

section {
   position: relative;
   border-top: 52px solid transparent; /* navbar height +2 */
   margin: -30px 0 0;
   -webkit-background-clip: padding-box;
   -moz-background-clip: padding;
   background-clip: padding-box;
}

section:before {
   content: "";
   position: absolute;
   top: -2px;
   left: 0;
   right: 0;
   border-top: 2px solid transparent;
}

Replace section by a class if you prefer.

source: Jump links and viewport positioning

  • Tested on Firefox 45 and Chrome 52.
  • bootstrap version: 3.3.7

For those who do not believe me I kindly prepared a jsfiddle with the solution in it: SOLUTION

CSS trick will be a workaround. A proper solution which will work in all scenario can be implemented using jQuery.

Refer to https://codepen.io/pikeshmn/pen/mMxEdZ

Approach: We get the height of fixed nav using document.getElementById('header').offsetHeight And offset the scroll to this value.

var jump=function(e){  

e.preventDefault();                        //prevent "hard" jump
  var target = $(this).attr("href");       //get the target

      //perform animated scrolling
      $('html,body').animate(
        {
          scrollTop: $(target).offset().top - document.getElementById('header').offsetHeight - 5  //get top-position of target-element and set it as scroll target
        },1000,function()                  //scrolldelay: 1 seconds
        {
          location.hash = target;          //attach the hash (#jumptarget) to the pageurl
        });
      }

  $(document).ready(function()
  {
    $('a[href*="#"]').bind("click", jump); //get all hrefs
    return false;
  });

P.S:

  • It includes a nice 5 pixels difference between header and target
  • Scroll effect is not hard, rather smooth; Smooth Scrolling
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top