Skip to content Skip to sidebar Skip to footer

Change Url Hash On Scroll And Keep Back Button Working

On a one page layout with fixed top menu and anchor navigation I have a 'scrollspy' in place that changes the fragment identifier on scroll, gives the menu link an active class dep

Solution 1:

To answer the first part of your question, if you don't want to pollute the browser's history, you can use history.replaceState() instead of history.pushState(). While pushState changes the URL and adds a new entry to the browser's history, replaceState changes the URL while modifying the current history entry instead of adding a new one.

There is also a good article including differences between pushState and replaceStateon MDN.

Solution 2:

For older browsers I decided to include https://github.com/devote/HTML5-History-API and with this in place I got the desired behaviour (more or less).

This answers has: - a scroll spy for the menu items and sets and active class to those on scroll - the scroll spy also works for the URL hash, setting the correct hash depending on the section that is currently scrolled to - a scroll stop function that checks when the user has stopped scrolling and then takes the value form the currently active menu item and sets that as the current URL hash. This is done on purpose to not catch the sections' anchors while scrolling but only the anchor of the section that the user scrolls to. - a smooth scroll with Velocity.js when clicking on the menu links as well as when using the back and forward buttons - functions that reacts to loading and reloading the page, meaning if you enter the page with a specific URL hash for a section it will animate the scroll to that section and if the page is reloaded it will animate the scroll to the top of the current section

The code is a rough sketch and could possibly use a few tweaks, this is just for demo purpose. I think I am still a beginner to please point out obvious errors so that I can learn from those. All links to where code snippets come from are included as well.

// In-Page Scroll Animation to Menu Link on click// ------------------------------------------------ //// https://john-dugan.com/fixed-headers-with-hash-links/// https://stackoverflow.com/questions/8355673/jquery-how-to-scroll-an-anchor-to-the-top-of-the-page-when-clicked// http://stackoverflow.com/a/8579673/1010918// $('a[href^="#"]').on('click', function(e) {
$('.menu-a').on('click', function(e) {

    // default = make hash appear right after click// not default = make hash appear after scrolling animation is finished
    e.preventDefault();

    var hash  = this.hash,
        $hash = $(hash)

    $hash.velocity("scroll", { duration: 700, easing: [ .4, .21, .35, 1 ], queue: false });
});



// In-Page Scroll Animation to Hash on Load and Reload// ----------------------------------------------------------- //// https://stackoverflow.com/questions/680785/on-window-location-hash-change// hashchange triggers popstate !
$(window).on('load', function(e) {

    var hash  = window.location.hash;
    console.log('hash on window load '+hash);
    $hash = $(hash)

    $hash.velocity("scroll", { duration: 500, easing: [ .4, .21, .35, 1 ], queue: false });

    // if not URL hash is present = root, go to top of page on reloadif (!window.location.hash){
        $('body').velocity("scroll", { duration: 500, easing: [ .4, .21, .35, 1 ], queue: false });
    }   
});



// In-Page Scroll Animation to Hash on Back or Forward Button// ---------------------------------------------------------- //
$('.menu-a').on('click', function(e) {  
    e.preventDefault(); 
    // keep the link in the browser history// set this separately from scrolling animation so it works in IE8
    history.pushState(null, null, this.href);
    returnfalse
}); 
$(window).on('popstate', function(e) {
    // alert('popstate fired');
    $('body').append('<div class="console1">popstate fired</div>');
    $('.console1').delay(1000).fadeOut('slow');

    if (!window.location.hash){
        $('body').append('<div class="console2">no window location hash present</div>');

        $('body').velocity("scroll", { duration: 700, easing: [ .4, .21, .35, 1 ], queue: false });

        $('.console2').delay(1000).fadeOut('slow');
    }

    console.log('window.location.hash = '+window.location.hash);
    var hash  = window.location.hash;
    $hash = $(hash)

    $hash.velocity("scroll", { duration: 700, easing: [ .4, .21, .35, 1 ], queue: false });
});



// ScrollSpy for Menu items only - gives selected Menu items the active class// ------------------------------------------------------------------------ //// Does not update fragment identifier in URL https://en.wikipedia.org/wiki/Fragment_identifier// https://jsfiddle.net/mekwall/up4nu/var lastId,

    // Anchors corresponding to menu items
    scrollItems = $menuLink.map(function(){
        var item = $($(this).attr("href"));
        // console.table(item);if (item.length) { return item; }
    });

    // Give menu item the active class on load of corresponding itemfunctionscrollSpy () {

        // Get container scroll positionvar fromTop = $(this).scrollTop()+ $menuButtonHeight;

        // Get id of current scroll itemvar cur = scrollItems.map(function(){
            if ($(this).offset().top < fromTop)
            returnthis;
        });

        // Get the id of the current element
        cur = cur[cur.length - 1];
        var id = cur && cur.length ? cur[0].id : "";

        if (lastId !== id) {
            lastId = id;
            // Set/remove active class
            $menuLink
            .parent().removeClass("active").end()
            .filter("[href='#"+id+"']").parent().addClass("active");
        }

        // Active Menu Link href Attribute
        activeMenuLinkHref = $('.menu-li.active > .menu-a').attr('href');
        // console.log('activeMenuLinkHref '+activeMenuLinkHref);   
    }
    scrollSpy()

    $(window).scroll(function(e){
        scrollSpy()
    });



// On Stop of Scrolling get Active Menu Link Href and set window.location.hash// --------------------------------------------------------------------------- //// Scroll Stop Function//---------------------//// https://stackoverflow.com/questions/8931605/fire-event-after-scrollling-scrollbars-or-mousewheel-with-javascript// http://stackoverflow.com/a/8931685/1010918// http://jsfiddle.net/fbSbT/1/// http://jsfiddle.net/fbSbT/87/

(function(){ 
    var special = jQuery.event.special,
        uid1 = 'D' + (+newDate()),
        uid2 = 'D' + (+newDate() + 1); 
    special.scrollstart = {
        setup: function() { 
            var timer,
                handler =  function(evt) { 
                    var _self = this,
                        _args = arguments; 
                    if (timer) {
                        clearTimeout(timer);
                    } else {
                        evt.type = 'scrollstart';
                        // throws "TypeError: jQuery.event.handle is undefined"// jQuery.event.handle.apply(_self, _args);// solution// http://stackoverflow.com/a/20809936/1010918// replace jQuery.event.handle.apply with jQuery.event.dispatch.apply
                        jQuery.event.dispatch.apply(_self, _args);
                    } 
                    timer = setTimeout( function(){
                        timer = null;
                    }, special.scrollstop.latency); 
                }; 
            jQuery(this).bind('scroll', handler).data(uid1, handler); 
        },
        teardown: function(){
            jQuery(this).unbind( 'scroll', jQuery(this).data(uid1) );
        }
    }; 
    special.scrollstop = {
        latency: 250,
        setup: function() { 
            var timer,
                    handler = function(evt) { 
                    var _self = this,
                        _args = arguments; 
                    if (timer) {
                        clearTimeout(timer);
                    }
                     timer = setTimeout( function(){ 
                        timer = null;
                        evt.type = 'scrollstop';                        
                        // throws "TypeError: jQuery.event.handle is undefined"// jQuery.event.handle.apply(_self, _args);// solution// http://stackoverflow.com/a/20809936/1010918// replace jQuery.event.handle.apply with jQuery.event.dispatch.apply
                        jQuery.event.dispatch.apply(_self, _args); 
                    }, special.scrollstop.latency); 
                }; 
            jQuery(this).bind('scroll', handler).data(uid2, handler); 
        },
        teardown: function() {
            jQuery(this).unbind( 'scroll', jQuery(this).data(uid2) );
        }
    };

})();



// Scroll Stop Function Called//----------------------------//

$(window).on("scrollstop", function() {

    // window.history.pushState(null, null, activeMenuLinkHref);// window.history.replaceState(null, null, activeMenuLinkHref);// http://stackoverflow.com/a/1489802/1010918 //// works best really
    hash = activeMenuLinkHref.replace( /^#/, '' );
    console.log('hash '+hash);
    var node = $( '#' + hash );
    if ( node.length ) {
      node.attr( 'id', '' );
      // console.log('node.attr id'+node.attr( 'id', '' ));
    }
    document.location.hash = hash;
    if ( node.length ) {
      node.attr( 'id', hash );
    }
});

CSS

.console1{
    position: fixed;
    z-index: 9999;
    top:0;
    right:0;    
    background-color: #fff;
    border: 2px solid red;
}

.console2{
    position: fixed;
    z-index: 9999;
    bottom:0;
    right:0;    
    background-color: #fff;
    border: 2px solid red;
}

I will also supply a jsfiddle in due time. ;)

Post a Comment for "Change Url Hash On Scroll And Keep Back Button Working"