Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Twitter Bootstrap sticky subnav becomes fixed before it should on shorter pages

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.

like image 388
wisew Avatar asked Jul 08 '12 04:07

wisew


1 Answers

I've also taken the same JS codes, and adapted it to my website (still under construction). Here's some of the codes (working)

HTML

<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>

LESS (CSS)

.fixed_on_top {
    left: 0;
    right: 0;
    z-index: 1020;
    position: fixed;
    top:$navbarHeight;
}

JavaScript

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.

like image 154
Nadim Avatar answered Oct 25 '22 20:10

Nadim