I've implemented an auto-floating subnav bar, taken straight from the Bootstrap docs CSS and JS, in a site I'm working on. It only appears on one view, but this is a Rails view, so it is dynamically generated depending on which object is loaded.
What I've found is that, when the content that appears below the subnav bar is sufficiently long, the behavior of the subnav bar works as expected - the subnav-fixed class is added right at the moment when the subnav bar scrolls out of view.
If the page is any shorter than this however, the subnav bar will become fixed before it's actually out of view, which creates a pretty jarring jump, not to mention you can see the space where the bar used to be, which you shouldn't be able to see.
I should add that I'm using a fixed (main) navbar, with the proper body padding accounted for.
It seems that the problem is with the $('.subnav').offset.top
value that is returned.
Could anyone who's better with jQuery / JS maybe help diagnose this, and come up with a way to make the subnav bar only become fixed when it is scrolled out of view?
The Javascript:
var $win = $(window)
, $nav = $('.subnav')
, navTop = $('.subnav').length && $('.subnav').offset().top
, isFixed = 0
, $hiddenName = $('.subnav .hide')
processScroll()
$nav.on('click', function () {
if (!isFixed) setTimeout(function () { $win.scrollTop($win.scrollTop() - 47) }, 10)
})
$win.on('scroll', processScroll)
function processScroll() {
var i, scrollTop = $win.scrollTop()
if (scrollTop >= navTop && !isFixed) {
isFixed = 1
$nav.addClass('subnav-fixed')
$hiddenName.removeClass('hide')
if (!$('.subnav li').hasClass('active')) {
$('.subnav li:eq(1)').addClass('active')
}
} else if (scrollTop <= navTop && isFixed) {
isFixed = 0
$nav.removeClass('subnav-fixed')
$hiddenName.addClass('hide')
$('.subnav li').removeClass('active')
}
}
Replicating Bootstraps main nav and subnav - I've seen this question, but I don't think it avoids the problem since it still uses .offset()
UPDATE:
Still no luck. navTop
still seems to get shorter as the page length gets shorter, which doesn't make any sense since it's using offset().top
. Is there something about how that method works that I'm not getting?
UPDATE 2
It seems like this might get resolved with Bootstrap 2.1.0, since it'll actually include a subnav bar as a real component, as well as a jQuery plugin for handling the sticky behavior. Still, I'd like to understand why the offset() function is so unreliable.
I've also taken the same JS codes, and adapted it to my website (still under construction). Here's some of the codes (working)
<div class="fix_on_top">
<div class="container">
<ul class="breadcrumb">
<li><a href="#">Home</a> <span class="divider">/</span></li>
<li><a href="#">Packages</a> <span class="divider">/</span></li>
<li class="active">Professional courses - I am a breadcrumb; I'll fix myself below the top navbar when you scroll the page</li>
</ul>
</div>
</div>
.fixed_on_top {
left: 0;
right: 0;
z-index: 1020;
position: fixed;
top:$navbarHeight;
}
var $window = $(window),
$fix_on_top = $('.fix_on_top'),
fixed_Top = $('.fix_on_top').length && $('.fix_on_top').offset().top - 40,
isFixed = 0;
process_scroll();
// hack sad times - holdover until rewrite for 2.1
$fix_on_top.on('click', function () {
if (!isFixed) setTimeout(function () {
$window.scrollTop($window.scrollTop() - 47)
}, 10);
});
$window.on('scroll', process_scroll);
function process_scroll()
{
var i, scrollTop = $window.scrollTop();
if (scrollTop >= fixed_Top && !isFixed) {
isFixed = 1;
$fix_on_top.addClass('fixed_on_top');
}
else if (scrollTop <= fixed_Top && isFixed)
{
isFixed = 0;
$fix_on_top.removeClass('fixed_on_top');
}
}
I've also adapted the original code (for another website) that works on THEAD (tables - i have one table per webpage with id = "tablesortable")
// Automatically add the class "fix_on_scroll" on #tablesortable's thead
if ($('#tablesortable thead').length)
{
var thead_cells = new Array();
$('#tablesortable thead').addClass('fix_on_scroll');
// Get the width of each cells in thead
$('#tablesortable thead').find('td, th').each(function(i, v) {
thead_cells.push($(this).width());
});
// Keep same with in tbody and tfoot
$('#tablesortable tbody tr:first').children('td, th').each(function(i, v) {
$(this).width(thead_cells[i]);
});
$('#tablesortable tfoot tr:first').children('td, th').each(function(i, v) {
$(this).width(thead_cells[i]);
});
}
// Fix all elements (with class .fix_on_scroll) just below the top menu on scroll
// (Modified version from homepage of Twitter's Bootstrap - js/application.js)
var $window = $(window),
$fix_on_scroll = $('.fix_on_scroll'),
fixed_elem_top = $('.fix_on_scroll').length && $('.fix_on_scroll').offset().top - 26,
isFixed = 0;
process_scroll();
// hack sad times - holdover until rewrite for 2.1
$fix_on_scroll.on('click', function () {
if (!isFixed) setTimeout(function () {$window.scrollTop($window.scrollTop() - 40)}, 10)
});
$window.on('scroll', process_scroll);
function process_scroll()
{
var i, scrollTop = $window.scrollTop();
if (scrollTop >= fixed_elem_top && !isFixed)
{
isFixed = 1;
$fix_on_scroll.addClass('fixed_on_scroll');
// Keep original width of td/th
if ($fix_on_scroll.is('thead'))
{
$fix_on_scroll.find('td, th').each(function(i, v) {
$(this).width(thead_cells[i]);
});
}
}
else if (scrollTop <= fixed_elem_top && isFixed)
{
isFixed = 0
$fix_on_scroll.removeClass('fixed_on_scroll')
}
}
My (adapted) codes are working. You can use them.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With