Search code examples
javascriptbrowser-historyhtml5-history

Browser goes forward two steps with history.pushState


I'm currently making a gallery with JavaScript. I want to create a custom history entry, so that the enduser can click the history back for closing a big-picture overlay.

My window.onpopstate looks like this:

   function stateChange(event){
    let state = event.state;
    console.log(state);
    if(state !== null){
        if(state.show){
            showOverlay(state.obj);
        }else{            
            hideOverlay();
        }
    }else{
        hideOverlay();
    }
}

And my eventListener for the pictures:

    let state = {show: true, obj: itemLinks[i].dataset.file};
    history.pushState(state, '');

The Problem is, that my console fires one null following after one click on a picture. But, when I then go back in the history it shows the state object. If I go back another time, I get the null, where I had started.

-- EDIT --

More of my code:

for (let i = 0, len = itemLinks.length; i < len; i++) {
    itemLinks[i].addEventListener('click', function(){
        let state = {show: true, obj: itemLinks[i].dataset.file};
        history.pushState(state, '');
    });
}
overlay.addEventListener('click', clickOverlay)
window.onpopstate = stateChange;

And:

let itemLinks = document.getElementsByClassName('item-link');

let overlay = document.getElementById('gallery-overlay');
let overlayImg = document.getElementById('gallery-overlay-img');    

function showOverlay(src){
    overlayImg.src = src;
    overlay.classList.add('gallery-overlay__show');
}

function hideOverlay(){
    overlay.classList.remove('gallery-overlay__show');
}

function clickOverlay(){
    history.back();
}

Solution

  • The issue was that the elements being clicked to trigger the navigation were <a> tags, which will trigger a navigation event if they are clicked and their href attribute is set.

    To avoid this you can change the element from an <a> tag to something else like <span> or an inline <div>, but this can bring usability concerns if your page ever needs to be navigable without a mouse. A more robust solution is to prevent the click event from causing navigation, using event.preventDefault():

    let itemLinks = document.getElementsByClassName('item-link');
    
    for (let link of itemLinks) {
      link.addEventListener('click', function(event){
        // Keep the <a> tag from triggering the default navigation
        event.preventDefault();
    
        // Push our own state navigation
        let state = {show: true, obj: link.dataset.file};
        history.pushState(state, '');
      });
    }
    

    This way you still keep all of the beneficial behavior of <a> tags without the pesky extra navigation event.