Search code examples
javascriptjqueryscrolltopsticky

Fixed header highlighted link behaviour not 100% correct


For a fixed header I add/remove an active class to the anchors like this:

// Store basic variables:
var win = $(window),
    sec = $('section'),
    nav = $('nav'),
    anc = $('nav a'),
    pos = nav.offset().top, // Distance of navigation to top of page
    arr = sec.map(function(){return $(this).offset().top}).get(); // Distance of each section to top of page in an array

// Make function to add/remove classes:
win.scroll(function(){
    var t = win.scrollTop(); // Distance of window top to top of page
    t > pos ? nav.addClass('sticky') : nav.removeClass('sticky'), // Compare viewport top with top of navigation
    // Compare each section position:
    $.each(arr, function(i, val) {
        (t >= Math.floor(val) && t < (val + sec.eq(i).outerHeight(true))) ? anc.eq(i-1).addClass('active')
        : anc.eq(i-1).removeClass('active')
    })
})

On some sections however at the very beginning of the section (i.e. after clicking the anchor and not scrolling further) the active class of the previous section (which is not in viewport anymore) won't get removed. Most probably due to calculations returning significant digits?

How can I get the calculations right so only the current section in viewport gets its anchor highlighted?


Solution

  • While I found some very weird behaviour during debugging this, it's as simple as substracting one pixel from the height of the section:

    t >= val && t < (val + sec.eq(i).outerHeight(true) -1) ? ...
    

    The rounding now happens directly inside var arr: return Math.floor($(this).offset().top)


    What's really weird here is that even though I rounded everything down in the end the less than condition got returned true even though it mathematically wasn't...
    For example this would return true (which apparently isn't):

    1824 >= 912 && 1824 < (912 + 912)
    

    So I had to substract 1px to make this true after all.


    To me it seems as if jQuery's .outerHeight() prints no significant digits, but does include them. As in the documentation it says:
    "The numbers returned by dimensions-related APIs, including .outerHeight(), may be fractional in some cases. Code should not assume it is an integer."

    Which gets weird when rounding down doesn't work on it. In my fiddle I put the height of the sections to 912.453125px and .outerHeight() returned 912 but rounding it down with Math.floor() still seemed to return the fractions although it wouldn't print. (See this fiddle, where, when you go to section two and press the debug button, the calculation would be the above example)

    So yeah, whatever. I'd like to have a more logical solution but substracting a pixel works.