Popstate al caricamento della pagina in Chrome


94

Sto utilizzando l'API Cronologia per la mia app Web e ho un problema. Eseguo chiamate Ajax per aggiornare alcuni risultati sulla pagina e utilizzo history.pushState () per aggiornare la barra degli indirizzi del browser senza ricaricare la pagina. Quindi, ovviamente, uso window.popstate per ripristinare lo stato precedente quando si fa clic sul pulsante Indietro.

Il problema è ben noto: Chrome e Firefox trattano l'evento popstate in modo diverso. Anche se Firefox non lo avvia al primo caricamento, Chrome lo fa. Vorrei avere lo stile di Firefox e non attivare l'evento durante il caricamento poiché aggiorna i risultati con esattamente gli stessi durante il caricamento. C'è una soluzione alternativa ad eccezione dell'uso di History.js ? Il motivo per cui non mi sento di usarlo è: ha bisogno di troppe librerie JS da solo e, poiché ho bisogno che sia implementato in un CMS con già troppo JS, vorrei ridurre al minimo JS che ci sto inserendo .

Quindi, vorrei sapere se esiste un modo per fare in modo che Chrome non attivi "popstate" al caricamento o, forse, qualcuno ha provato a utilizzare History.js poiché tutte le librerie sono state riunite in un unico file.


3
Per aggiungere, il comportamento di Firefox è il comportamento specificato e corretto. A partire da Chrome 34, ora è stato risolto! Yay for the Opera devs!
Henry Chan

Risposte:


49

In Google Chrome nella versione 19 la soluzione di @spliter ha smesso di funzionare. Come ha sottolineato @johnnymire, history.state in Chrome 19 esiste, ma è nullo.

La mia soluzione è aggiungere window.history.state! == null per verificare se lo stato esiste in window.history:

var popped = ('state' in window.history && window.history.state !== null), initialURL = location.href;

L'ho testato in tutti i principali browser e nelle versioni 19 e 18 di Chrome. Sembra che funzioni.


5
Grazie per il testa a testa; Penso che tu possa semplificare ine nullcontrollare con solo != nullperché anche questo si occuperà undefined.
pimvdb

5
Usarlo funzionerà solo se usi sempre nullnei tuoi history.pushState()metodi come history.pushState(null,null,'http//example.com/). Certo, probabilmente la maggior parte degli usi (è così che è impostato in jquery.pjax.js e nella maggior parte delle altre demo). Ma se i browser implementano window.history.state(come FF e Chrome 19+), window.history.state potrebbe non essere nullo su un aggiornamento o dopo un riavvio del browser
inspiegabileBacn

19
Questo non funziona più per me in Chrome 21. Ho finito per impostare saltato su falso al caricamento della pagina e quindi impostarlo su vero su qualsiasi push o pop. Questo neutralizza il pop di Chrome al primo caricamento. È spiegato un po 'meglio qui: github.com/defunkt/jquery-pjax/issues/143#issuecomment-6194330
Chad von Nau

1
@ChadvonNau ottima idea, e funziona benissimo - grazie mille!
sowasred2012

Sono venuto con lo stesso problema e ho finito usando la semplice soluzione @ChadvonNau.
Pherrymason

30

Nel caso in cui non desideri prendere misure speciali per ogni gestore che aggiungi a onpopstate, la mia soluzione potrebbe essere interessante per te. Un grande vantaggio di questa soluzione è anche che gli eventi onpopstate possono essere gestiti prima che il caricamento della pagina sia stato completato.

Esegui questo codice una volta prima di aggiungere qualsiasi gestore onpopstate e tutto dovrebbe funzionare come previsto (ovvero come in Mozilla ^^) .

(function() {
    // There's nothing to do for older browsers ;)
    if (!window.addEventListener)
        return;
    var blockPopstateEvent = document.readyState!="complete";
    window.addEventListener("load", function() {
        // The timeout ensures that popstate-events will be unblocked right
        // after the load event occured, but not in the same event-loop cycle.
        setTimeout(function(){ blockPopstateEvent = false; }, 0);
    }, false);
    window.addEventListener("popstate", function(evt) {
        if (blockPopstateEvent && document.readyState=="complete") {
            evt.preventDefault();
            evt.stopImmediatePropagation();
        }
    }, false);
})();

Come funziona:

Cromo , Safari e probabilmente altri browser webkit attivano l'evento onpopstate quando il documento è stato caricato. Questo non è previsto, quindi blocchiamo gli eventi popstate fino al primo ciclo del ciclo di eventi dopo che il documento è stato caricato. Ciò viene eseguito dalle chiamate preventDefault e stopImmediatePropagation (a differenza di stopPropagation, stopImmediatePropagation arresta istantaneamente tutte le chiamate del gestore eventi).

Tuttavia, poiché readyState del documento è già su "complete" quando Chrome si attiva erroneamente supopstate, consentiamo eventi opopstate, che sono stati attivati ​​prima che il caricamento del documento sia terminato, per consentire chiamate onpopstate prima che il documento sia stato caricato.

Aggiornamento 23-04-2014: corretto un bug per il quale gli eventi popstate venivano bloccati se lo script veniva eseguito dopo che la pagina era stata caricata.


4
La migliore soluzione finora per me. Uso il metodo setTimeout da anni, ma provoca un comportamento difettoso quando la pagina si carica lentamente / quando l'utente attiva pushstate molto rapidamente. window.history.statenon riesce al ricaricamento se è stato impostato uno stato. Impostazione della variabile prima del codice di cluster pushState. La tua soluzione è elegante e può essere rilasciata sopra altri script, senza influire sul resto del codice. Grazie.
kik

4
Questa è LA soluzione! Se solo leggessi fino a questo punto giorni fa quando provo a risolvere questo problema. Questo è l'unico che ha funzionato perfettamente. Grazie!
Ben Fleming

1
Ottima spiegazione e l'unica soluzione che vale la pena provare.
Sunny R Gupta

Questa è un'ottima soluzione. Al momento della scrittura, l'ultimo Chrome e FF sono a posto, il problema riguarda Safari (Mac e iPhone). Questa soluzione funziona come un fascino.
Vin

1
Woot! Se Safari ti sta rovinando la giornata, questi sono i codici che stai cercando!
DanO

28

Usare solo setTimeout non è una soluzione corretta perché non hai idea di quanto tempo ci vorrà per il caricamento del contenuto, quindi è possibile che l'evento popstate venga emesso dopo il timeout.

Ecco la mia soluzione: https://gist.github.com/3551566

/*
* Necessary hack because WebKit fires a popstate event on document load
* https://code.google.com/p/chromium/issues/detail?id=63040
* https://bugs.webkit.org/process_bug.cgi
*/
window.addEventListener('load', function() {
  setTimeout(function() {
    window.addEventListener('popstate', function() {
      ...
    });
  }, 0);
});

Questa è l'unica soluzione che ha funzionato per me. Grazie!
Justin

3
dolce! Funziona meglio della risposta più votata e / o accettata!
ProblemsOfSumit

Qualcuno ha il collegamento attivo a questa soluzione, non riesco a raggiungere il collegamento indicato gist.github.com/3551566
Harbir

1
perché hai bisogno del succo?
Oleg Vaskevich

Risposta perfetta. Grazie.
sideroxylon

25

La soluzione è stata trovata in jquery.pjax.js linee 195-225:

// Used to detect initial (useless) popstate.
// If history.state exists, assume browser isn't going to fire initial popstate.
var popped = ('state' in window.history), initialURL = location.href


// popstate handler takes care of the back and forward buttons
//
// You probably shouldn't use pjax on pages with other pushState
// stuff yet.
$(window).bind('popstate', function(event){
  // Ignore inital popstate that some browsers fire on page load
  var initialPop = !popped && location.href == initialURL
  popped = true
  if ( initialPop ) return

  var state = event.state

  if ( state && state.pjax ) {
    var container = state.pjax
    if ( $(container+'').length )
      $.pjax({
        url: state.url || location.href,
        fragment: state.fragment,
        container: container,
        push: false,
        timeout: state.timeout
      })
    else
      window.location = location.href
  }
})

8
Questa soluzione ha smesso di funzionare per me su Windows (Chrome 19), ma funziona ancora su Mac (Chrome 18). Sembra che history.state esista in Chrome 19 ma non 18.
johnnymire

1
@johnnymire ho postato la mia soluzione per Chrome 19 inferiori: stackoverflow.com/a/10651028/291500
Pavel Linkesch

Qualcuno dovrebbe modificare questa risposta per riflettere questo comportamento post-Chrome 19.
Jorge Suárez de Lis

5
Per chiunque cerchi una soluzione, la risposta di Torben funziona a meraviglia sulle attuali versioni di Chrome e Firefox a partire da oggi
Jorge Suárez de Lis

1
Ora, nell'aprile 2015, ho utilizzato questa soluzione per risolvere un problema con Safari. Ho dovuto utilizzare l'evento onpopstate per ottenere una gestione premendo il pulsante Indietro su un'app ibrida iOS / Android utilizzando Cordova. Safari spara il suo onpopstate anche dopo un ricaricamento, che ho chiamato a livello di programmazione. Con il modo in cui è stato impostato il mio codice, è entrato in un ciclo infinito dopo che è stato chiamato reload (). Questo l'ha risolto. Avevo bisogno solo delle prime 3 righe dopo la dichiarazione dell'evento (più le assegnazioni in alto).
Martavis P.

14

Una soluzione più diretta rispetto alla reimplementazione di pjax è impostare una variabile su pushState e controllare la variabile su popState, in modo che il popState iniziale non si attivi in ​​modo incoerente al caricamento (non una soluzione specifica di jquery, usandola solo per gli eventi):

$(window).bind('popstate', function (ev){
  if (!window.history.ready && !ev.originalEvent.state)
    return; // workaround for popstate on load
});

// ... later ...

function doNavigation(nextPageId) {
  window.history.ready = true;

  history.pushState(state, null, 'content.php?id='+ nextPageId); 
  // ajax in content instead of loading server-side
}

Non sarebbe che valesse sempre come falso? Voglio dire, window.history.readyè sempre vero.
Andriy Drozdyuk

Aggiornato l'esempio per essere un po 'più chiaro. Fondamentalmente, vuoi solo gestire gli eventi popstate solo dopo aver dato il tutto chiaro. Potresti ottenere lo stesso effetto con un setTimeout 500 ms dopo il caricamento, ma ad essere onesti è un po 'economico.
Tom McKenzie

3
Questa dovrebbe essere la risposta IMO, ho provato la soluzione Pavels e non ha funzionato correttamente in Firefox
Porco

Questo ha funzionato per me come una facile soluzione a questo fastidioso problema. Molte grazie!
nirazul

!window.history.ready && !ev.originalEvent.state- queste due cose non vengono mai definite quando aggiorno la pagina. quindi quindi nessun codice viene eseguito.
chovy

4

L' evento onpopstate iniziale di Webkit non ha uno stato assegnato, quindi puoi usarlo per verificare il comportamento indesiderato:

window.onpopstate = function(e){
    if(e.state)
        //do something
};

Una soluzione completa, che consenta di tornare alla pagina originale, si baserebbe su questa idea:

<body onload="init()">
    <a href="page1" onclick="doClick(this); return false;">page 1</a>
    <a href="page2" onclick="doClick(this); return false;">page 2</a>
    <div id="content"></div>
</body>

<script>
function init(){
   openURL(window.location.href);
}
function doClick(e){
    if(window.history.pushState)
        openURL(e.getAttribute('href'), true);
    else
        window.open(e.getAttribute('href'), '_self');
}
function openURL(href, push){
    document.getElementById('content').innerHTML = href + ': ' + (push ? 'user' : 'browser'); 
    if(window.history.pushState){
        if(push)
            window.history.pushState({href: href}, 'your page title', href);
        else
            window.history.replaceState({href: href}, 'your page title', href);
    }
}
window.onpopstate = function(e){
    if(e.state)
        openURL(e.state.href);
};
</script>

Anche se questo potrebbe ancora essere attivato due volte (con una navigazione intelligente), può essere gestito semplicemente con un controllo rispetto al precedente href.


1
Grazie. La soluzione pjax ha smesso di funzionare su iOS 5.0 perché window.history.stateanche in iOS è nullo. Basta controllare se la proprietà e.state è null è sufficiente.
Husky

Ci sono problemi noti con questa soluzione? Funziona perfettamente su tutti i browser che provo.
Jacob Ewing

3

Questa è la mia soluzione alternativa.

window.setTimeout(function() {
  window.addEventListener('popstate', function() {
    // ...
  });
}, 1000);

Non per me su Chromium 28 su Ubuntu. A volte, l'evento è ancora attivato.
Jorge Suárez de Lis

Ho provato io stesso, non sempre funziona. A volte il popstate si attiva più tardi del timeout a seconda del caricamento della pagina iniziale
Andrew Burgess,

1

Ecco la mia soluzione:

var _firstload = true;
$(function(){
    window.onpopstate = function(event){
        var state = event.state;

        if(_firstload && !state){ 
            _firstload = false; 
        }
        else if(state){
            _firstload = false;
            // you should pass state.some_data to another function here
            alert('state was changed! back/forward button was pressed!');
        }
        else{
            _firstload = false;
            // you should inform some function that the original state returned
            alert('you returned back to the original state (the home state)');
        }
    }
})   


0

In caso di utilizzo, il event.state !== nullritorno alla cronologia alla prima pagina caricata non funzionerà nei browser non mobili. Uso sessionStorage per contrassegnare quando la navigazione ajax inizia davvero.

history.pushState(url, null, url);
sessionStorage.ajNavStarted = true;

window.addEventListener('popstate', function(e) {
    if (sessionStorage.ajNavStarted) {
        location.href = (e.state === null) ? location.href : e.state;
    }
}, false);


window.onload = function () {if (history.state === null) {history.replaceState (location.pathname + location.search + location.hash, null, location.pathname + location.search + location.hash); }}; window.addEventListener ('popstate', function (e) {if (e.state! == null) {location.href = e.state;}}, false);
ilusionista

-1

Le soluzioni presentate hanno un problema durante il ricaricamento della pagina. Quanto segue sembra funzionare meglio, ma ho testato solo Firefox e Chrome. Usa la realtà, che sembra esserci una differenza tra e.event.statee window.history.state.

window.addEvent('popstate', function(e) {
    if(e.event.state) {
        window.location.reload(); // Event code
    }
});

e.eventè undefined
chovy

-1

So che hai chiesto di non farlo, ma dovresti davvero usare History.js perché elimina un milione di incompatibilità del browser. Ho seguito il percorso di correzione manuale solo per scoprire in seguito che c'erano sempre più problemi che scoprirai solo lungo la strada. Non è davvero così difficile al giorno d'oggi:

<script src="//cdnjs.cloudflare.com/ajax/libs/history.js/1.8/native.history.min.js" type="text/javascript"></script>

E leggi l'API su https://github.com/browserstate/history.js


-1

Questo ha risolto il problema per me. Tutto quello che ho fatto è stato impostare una funzione di timeout che ritarda l'esecuzione della funzione abbastanza a lungo da perdere l'evento popstate che viene attivato al pageload

if (history && history.pushState) {
  setTimeout(function(){
    $(window).bind("popstate", function() {
      $.getScript(location.href);
    });
  },3000);
}

-2

Puoi creare un evento e attivarlo dopo il tuo gestore onload.

var evt = document.createEvent("PopStateEvent");
evt.initPopStateEvent("popstate", false, false, { .. state object  ..});
window.dispatchEvent(evt);

Nota, questo è leggermente rotto in Chrome / Safari, ma ho inviato la patch a WebKit e dovrebbe essere disponibile presto, ma è il modo "più corretto".


-2

Questo ha funzionato per me in Firefox e Chrome

window.onpopstate = function(event) { //back button click
    console.log("onpopstate");
    if (event.state) {
        window.location.reload();
    }
};
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.