How do I set the offset for ScrollSpy in Bootstrap?

Twitter Bootstrap

Twitter Bootstrap Problem Overview


I have a site with the navbar fixed on top and 3 divs underneath in the main content area.

I'm trying to use scrollspy from the bootstrap framework.

I have it succesfully highlighting the different headings in the menu when you scroll past the divs.

I also have it so when you click the menu, it will scroll to the correct part of the page. However, the offset is incorrect (It's not taking into account the navbar, so I need to offset by about 40 pixels)

I see on the Bootstrap page it mentions an offset option but i'm not sure how to use it.

I'm also not what it means when it says you can use scrollspy with $('#navbar').scrollspy(), I'm not sure where to include it so I didn't and everything seems to be working (except the offset).

I thought the offset might be the data-offset='10' on the body tag, but it doesn't do anything for me.

I have a feeling that this is something really obvious and I'm just missing it. Any help?

My code is

...
<!-- note: the data-offset doesn't do anything for me -->
<body data-spy="scroll" data-offset="20">
<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
    <div class="container">
	<a class="brand" href="#">VIPS</a>
	<ul class="nav">
	    <li class="active">
	         <a href="#trafficContainer">Traffic</a>
	    </li>
	    <li class="">
		<a href="#responseContainer">Response Times</a>
	    </li>
	    <li class="">
		<a href="#cpuContainer">CPU</a>
	    </li>
	  </ul>
    </div>
</div>
</div>
<div class="container">
<div class="row">
    <div class="span12">
    <div id="trafficContainer" class="graph" style="position: relative;">
	<!-- graph goes here -->
    </div>
    </div>
</div>
<div class="row">
    <div class="span12">
    <div id="responseContainer" class="graph" style="position: relative;">
	<!-- graph goes here -->
    </div>
    </div>
</div>
<div class="row">
    <div class="span12">
    <div id="cpuContainer" class="graph" style="position: relative;">
	<!-- graph goes here -->
    </div>
    </div>
</div>
</div>

...
<script src="assets/js/jquery-1.7.1.min.js"></script>
<script src="assets/js/bootstrap-scrollspy.js"></script>
</body>
</html>

Twitter Bootstrap Solutions


Solution 1 - Twitter Bootstrap

Bootstrap uses offset to resolve spying only, not scrolling. This means that scrolling to the proper place is up to you.

Try this, it works for me: add an event handler for the navigation clicks.

var offset = 80;

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

I found it here: https://github.com/twitter/bootstrap/issues/3316

Solution 2 - Twitter Bootstrap

The trick, as Tim alluded to, is to take advantage of padding. So the problem is, your browser always wants to scroll your anchor to the exact top of the window. if you set your anchor where your text actually begins, it will be occluded by your menu bar. Instead, what you do is set your anchor to the container <section> or <div> tag's id, which the nav/navbar will automatically use. Then you have to set your padding-top for the container to the amount offset you want, and the margin-top for the container to the opposite of the padding-top. Now your container's block and the anchor begin at the exact top of the page, but the content inside doesn't begin until below the menu bar.

If you're using regular anchors, you can accomplish the same thing by using negative margin-top in your container as above, but no padding. Then you put a regular <a target="..."> anchor as the first child of the container. Then you style the second child element with an opposite but equal margin-top, but using the selector .container:first-child +.

This all presumes that your <body> tag already has its margin set to begin below your header, which is the norm (otherwise the page would render with occluded text right off the bat).

Here's an example of this in action. Anything on your page with an id can be linked to by tacking #the-elements-id onto the end of your URL. If you make all of your h tags ad dt tags with ids link-able (Like github does) with some sort of script that injects a little link to the left of them; adding the following to your CSS will take care of the nav-bar offset for you:

body {
    margin-top: 40px;/* make room for the nav bar */
}

/* make room for the nav bar */
h1[id],
h2[id],
h3[id],
h4[id],
h5[id],
h6[id],
dt[id]{
	padding-top: 60px;
	margin-top: -40px;
}

Solution 3 - Twitter Bootstrap

You can change the offset of scrollspy on the fly with this (assuming you spying on the body):

offsetValue = 40;
$('body').data().scrollspy.options.offset = offsetValue;
// force scrollspy to recalculate the offsets to your targets
$('body').data().scrollspy.process();

This also works if you need to change the offset value dynamically. (I like the scrollspy to switch over when the next section is about halfway up the window so I need to change the offset during window resize.)

Solution 4 - Twitter Bootstrap

If you want an offset of 10 you would put this in your head:

<script>
  $('#navbar').scrollspy({
    offset: 10
  });
</script>

Your body tag would look like this:

<body data-spy="scroll">

Solution 5 - Twitter Bootstrap

From bootstrap issue #3316:

> [This] is only intended to modify scrolling calculations - we don't shift the actual anchor click

So setting offset only affects where scrollspy thinks the page is scrolled to. It doesn't affect scrolling when you click an anchor tag.

Using a fixed navbar means that the browser is scrolling to the wrong place. You can fix this with JavaScript:

var navOffset = $('.navbar').height();

$('.navbar li a').click(function(event) {
    var href = $(this).attr('href');

    // Don't let the browser scroll, but still update the current address
    // in the browser.
    event.preventDefault();
    window.location.hash = href;

    // Explicitly scroll to where the browser thinks the element
    // is, but apply the offset.
    $(href)[0].scrollIntoView();
    window.scrollBy(0, -navOffset);
});

However, to ensure that scrollspy highlights the correct current element, you still need to set the offset:

<body class="container" data-spy="scroll" data-target=".navbar" data-offset="70">

Here's a complete demo on JSFiddle.


Alternatively, you could padding to your anchor tags, as suggested by CSS tricks.

a[id]:before { 
  display: block; 
  content: " ";
  // Use the browser inspector to find out the correct value here.
  margin-top: -285px; 
  height: 285px; 
  visibility: hidden; 
}

Solution 6 - Twitter Bootstrap

I think that offset might do something with the bottom position of the section.

I fixed my issue - the same as yours - by adding

  padding-top: 60px;

in the declaration for section in bootstrap.css

Hope that helps!

Solution 7 - Twitter Bootstrap

I added 40px-height .vspace element holding the anchor before each of my h1 elements.

<div class="vspace" id="gherkin"></div>
<div class="page-header">
  <h1>Gherkin</h1>
</div>

In the CSS:

.vspace { height: 40px;}

It's working great and the space is not chocking.

Solution 8 - Twitter Bootstrap

Bootstrap 4 (2019 update)

Implementing: Fixed Nav, Scrollspy, and Smooth Scrolling

This is the solution from @wilfred-hughes above, it exactly fixes the overlap issue with the Bootstrap Fixed Navbar by using CSS '::before' to prepend an non-displayed block before each section tag. Advantage: you don't end up with unnecessary padding between your sections. Eg, section 3 can be vertically positioned right up against section 2 and it will still scroll to the exact correct position at the top of section 3.

Side note: setting the scrollspy offset in the script tag caused nothing to happen ( $('#navbar').scrollspy({ offset: 105 });) and attempts to adjust the offset in the animation block still resulted in misalignment post-animation.

index.html

<section id="section1" class="my-5">
...
<section id="section2" class="my-5">
...
<section id="section3" class="my-5">
...

later in index.html...

 <script>
      // Init Scrollspy
      $('body').scrollspy({ target: '#main-nav' });

      // Smooth Scrolling
      $('#main-nav a').on('click', function(event) {
        if (this.hash !== '') {
          event.preventDefault();

          const hash = this.hash;

          $('html, body').animate(
            {
              scrollTop: $(hash).offset().top
            },
            800,
            function() {
              window.location.hash = hash;
            }
          );
        }
      });
    </script>

style.css:

// Solution to Fixed NavBar overlapping content of the section.  Apply to each navigable section.
// adjust the px values to your navbar height
// Source: https://github.com/twbs/bootstrap/issues/1768
#section1::before,
#section2::before,
#section3::before {
  display: block;
  content: ' ';
  margin-top: -105px;
  height: 105px;
  visibility: hidden;
}

body {
  padding-top: 105px;
}

Credit: @wilfred-hughes above and the original source from user @mnot at https://github.com/twbs/bootstrap/issues/1768

Solution 9 - Twitter Bootstrap

I had problems with the solutions of acjohnson55 and Kurt UXD. Both worked for clicking a link to the hash, but the scrollspy offset was still not correct.

I came up with the following solution:

<div class="anchor-outer">
  <div id="anchor">
    <div class="anchor-inner">
      <!-- your content -->
    </div>
  </div>
</div>

And the corresponding CSS:

body {
  padding-top:40px;
}

.anchor-outer {
  margin-top:-40px;
}

.anchor-inner {
  padding-top:40px;
}

@media (max-width: 979px) {
  body {
    padding-top:0px;
  }

  .anchor-outer {
    margin-top:0px;
  }

  .anchor-inner {
    padding-top:0px;
  }
}

@media (max-width: 767px) {
  body {
    padding-top:0px;
  }

  .anchor-outer {
    margin-top:0px;
  }

  .anchor-inner {
    padding-top:0px;
  }
}

You will need to replace the 40px padding / margin with the height of your navbar, and the id of your anchor.

In this setup I can even observe an effect of setting a value for the data-offset attribute. If find large values for the data-offset around 100-200 lead to a more expected behavior (the scrollspy-"switch" will happen if the heading is circa in the middle of the screen).

I also found that scrollspy is very sensitive to errors like unclosed div tags (which is quite obvious), so http://validator.w3.org/check was my friend here.

In my opinion scrollspy works best with whole sections carrying the anchor ID, since regular anchor elements do not lead to expected effects when scrolling backwards (the scrollspy-"switch" eventually happens as soon as you hit the anchor element, e.g. your heading, but at that time you have already scrolled over the content which the user expects to belong to the according heading).

Solution 10 - Twitter Bootstrap

Take a look on this post I made in another thread. This contains a snippet on how you can programmatically change the offset value and dynamically react on changes (e.g. if the navbar changes in size).

You don't need to tinker around with CSS fixes but rather set the offset you currently need in dependence of certain events.

Solution 11 - Twitter Bootstrap

You can also open bootstap-offset.js and modify

$.fn.scrollspy.defaults = {
  offset: 10
}

there.

Solution 12 - Twitter Bootstrap

Just set :

data-offset="200"

data-offset is by default "50" and that is equivalent 10px, so just increase data-offset and your problem is solved.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionLangoView Question on Stackoverflow
Solution 1 - Twitter BootstrapKurt UXDView Answer on Stackoverflow
Solution 2 - Twitter BootstrapacjayView Answer on Stackoverflow
Solution 3 - Twitter BootstrapcogellView Answer on Stackoverflow
Solution 4 - Twitter BootstrapDavid NaffisView Answer on Stackoverflow
Solution 5 - Twitter BootstrapWilfred HughesView Answer on Stackoverflow
Solution 6 - Twitter BootstrapTimView Answer on Stackoverflow
Solution 7 - Twitter BootstrapQuentinView Answer on Stackoverflow
Solution 8 - Twitter BootstrapObjectiveTCView Answer on Stackoverflow
Solution 9 - Twitter BootstrapschreonView Answer on Stackoverflow
Solution 10 - Twitter BootstrapthexView Answer on Stackoverflow
Solution 11 - Twitter BootstrapAdobeView Answer on Stackoverflow
Solution 12 - Twitter BootstrapZivanicView Answer on Stackoverflow