Search code examples
javascriptjqueryhtmltwitter-bootstrapsmooth-scrolling

Smooth-scroll a bootstrap page with sticky navbar


Smooth-scroll scripts in common, stop the default link action (by returning false) to prevent a jump in the animation. This also stops bootstrap navbar menus to collapse when clicked on a link (bad).

Example:
http://www.bootply.com/NoGAvuQGDE

In the smooth scroll script's last line;

  • if you return false, it conflicts and stops the menu closing when clicked on a link.
  • If you return true, the animation gets jumpy due to the link working.

Reproduce:

  • click on the dropdown / action link
  • the dropdown stays open (bad), due to the blocked default action

Excluding navbar links from smooth-scrolling would beat the purpose.

Idea:
We could modify the smooth-scroll script to close all navbar menus when a link is smooth-scrolled. Something like $('.dropdown-toggle').dropdown('collapse') would work but there is no 'collapse' action in the API, only 'toggle'.

Q: How can I have smooth-scrolling on a bootstrap page, without it conflicting with navbar menus, stopping them close?


The smoothscroll script, for future reference :

$(function(){
    $('.navbar a[href*=#], a.smooth').click(function() {return smoothScroll(this)});
});

function smoothScroll(linkItem) {

    if (location.pathname.replace(/^\//,'') == linkItem.pathname.replace(/^\//,'')
    && location.hostname == linkItem.hostname) {

        var $target = $(linkItem.hash);
        $target = $target.length && $target || $('[name=' + linkItem.hash.slice(1) +']');

        if ($target.length) {
        var targetOffset = $target.offset().top;
        $('html,body').animate({scrollTop: targetOffset}, 500);
        return false;
        }
    }
}

Solution

  • Looking at the document tree while toggling the dropdown menu, the Bootstrap functionality seems to rely on an open class on the parent. In the relevant code concerning the event handlers (which is a bit obscure to read), this class is toggled. The following would emulate it when the default behaviour is prevented on a menu that is active and seems to be doing the trick :

    dropdown = linkItem.closest('.dropdown');
    
    if ($(dropdown).hasClass('open')) $(dropdown).removeClass('open');
    

    One could argue the link is only clickable when the dropdown is open so even this should suffice :

    $(dropdown).toggleClass('open');
    

    A dropdown inside another one would need some additional script but I guess that's an unlikely configuration. Interaction with any siblings is apparently working well too. Integrated in the code :

    $(function(){
        $('.navbar a[href*=#]').click(function() {return smoothScroll(this)});
    });
    
    function smoothScroll(linkItem) {
    
        if (location.pathname.replace(/^\//,'') == linkItem.pathname.replace(/^\//,'')
        && location.hostname == linkItem.hostname) {
    
            var $target = $(linkItem.hash);
            $target = $target.length && $target || $('[name=' + linkItem.hash.slice(1) +']');
            var dropdown = linkItem.closest('.dropdown');
    
            if ($target.length) {
            if (dropdown) $(dropdown).toggleClass('open');
            var targetOffset = $target.offset().top;
            $('html,body').animate({scrollTop: targetOffset}, 500);
            return false;
            }
        }
    }
    

    When only using this script on anchor links, it could be simplified by the way :

    $(function(){
        $('.navbar a[href*=#]').click(function() {smoothScroll(this)});
    });
    
    function smoothScroll(linkItem) {
    
        var $target = $(linkItem.hash),
        dropdown = linkItem.closest('.dropdown');
    
        if ($target.length) {
        if (dropdown) $(dropdown).toggleClass('open');
        var targetOffset = $target.offset().top;
        $('html,body').animate({scrollTop: targetOffset}, 500);
        return false;
        }
    }
    

    While getting the expected results with what was posted in the comments earlier, after additional debugging the code needed to be a bit more subtle to find the correct parent - also added an extra if (dropdown) check. No need to apply a method on something that doesn't exist of course.

    http://www.bootply.com/hC4s6OEgA1