Unfortunately jQuery pulls in the data-*
attributes only once when the $().data(...)
function is called the first time. And scrollspy
only reads the options once when it is initialised. The refresh
function of scrollspy does not reload any of the options. Calling $(..).scrollspy(...)
again on the same element ignores any new data options (uses the previously initialized values).
However, scrollspy does store the options values in the elements data under the key 'bs.scrollspy'
. So you can alter the options.offset
field inside that data key.
To account for a dynamically changing navbar height and the need to alter the scrollspy offset value, you can use the following example for a variable height fixed-top navbar.
The following does a few of things.
- It initializes scrollspy via javascript (after the
window.load
event fires), and starts off with an offset of the navbar's current height (also adjusts the body's padding-top
value to be the same as the navbar height).
- Resize events are monitored and the body's
padding-top
is adjusted, and the scrollspy offset
is tweaked to match. Then a refresh
is performed to recalculate the anchor offsets.
HTML
<body>
<style type="text/css">
/* prevent navbar from collapsing on small screens */
#navtop .navbar-nav > li { float: left !important; }
</style>
<nav id="navtop" class="navbar-fixed-top" role="navigation">
<div class="container">
<ul class="nav nav-pills navbar-nav">
<li><a href="#one">One</a></li>
<li><a href="#two">Two</a></li>
<li><a href="#three">Three</a></li>
... some more navlinks, or dynamically added links that affect the height ...
</ul>
</div>
</nav>
<section id="one">
...
</section>
<section id="two">
...
</section>
<section id="three">
...
</section>
....
</body>
JavaScript
$(window).on('load',function(){
var $body = $('body'),
$navtop = $('#navtop'),
offset = $navtop.outerHeight();
// fix body padding (in case navbar size is different than the padding)
$body.css('padding-top', offset);
// Enable scrollSpy with correct offset based on height of navbar
$body.scrollspy({target: '#navtop', offset: offset });
// function to do the tweaking
function fixSpy() {
// grab a copy the scrollspy data for the element
var data = $body.data('bs.scrollspy');
// if there is data, lets fiddle with the offset value
if (data) {
// get the current height of the navbar
offset = $navtop.outerHeight();
// adjust the body's padding top to match
$body.css('padding-top', offset);
// change the data's offset option to match
data.options.offset = offset;
// now stick it back in the element
$body.data('bs.scrollspy', data);
// and finally refresh scrollspy
$body.scrollspy('refresh');
}
}
// Now monitor the resize events and make the tweaks
var resizeTimer;
$(window).resize(function() {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(fixSpy, 200);
});
});
And thats it. Not pretty, but it definitely works. My HTML above may need some tweaking.
If you add elements to your navbar dynamically you will need to call fixSpy()
after they are inserted. Or you could call fixSpy()
via a setInterval(...)
timer to run all the time.
data-
attributes it works fine. Only when I do it from Javascript do I get the "accumulating" offset effect. Somehow initializing scrollspy from JS like in the snippet above is not equivalent to letting it do it from thedata-
attributes. Haven't figured out why yet. – Florrespond.js
in IE8 for compatibility). Are you wrapping your js code inside the document ready handler? – Cover$('body').scrollspy({...})
inside of$(document).ready(function() {
. But as I mentioned above, doing it this way creates a different set of offsets compared to letting it initialize by itself with thedata-
attributes. – Flor$(document).ready
make the difference here. The plugin use$(window).on('load'
to set the data- attributes. See also: #4396280 – Podvin$(document).ready
happens earlier (often much earlier) than the$(window)
load event. So it's very likely that the heights haven't been fully adjusted yet. One option is to try hooking the offsets on the latter callback instead, but at this point I have opted for a completely different approach, adjusting CSS padding, etc. Thank you all for your help and insights. – Flor