Popstate beim Laden der Seite in Chrome

94

Ich verwende die Verlaufs-API für meine Web-App und habe ein Problem. Ich rufe Ajax auf, um einige Ergebnisse auf der Seite zu aktualisieren, und verwende history.pushState () , um die Standortleiste des Browsers zu aktualisieren, ohne die Seite neu zu laden. Dann benutze ich natürlich window.popstate , um den vorherigen Status wiederherzustellen, wenn auf die Schaltfläche "Zurück" geklickt wird.

Das Problem ist bekannt - Chrome und Firefox behandeln dieses Popstate-Ereignis unterschiedlich. Während Firefox es beim ersten Laden nicht startet, tut es Chrome. Ich möchte einen Firefox-Stil haben und das Ereignis nicht beim Laden auslösen, da es nur die Ergebnisse mit genau den gleichen beim Laden aktualisiert. Gibt es eine Problemumgehung außer der Verwendung von History.js ? Der Grund, warum ich keine Lust habe, es zu verwenden, ist, dass es viel zu viele JS-Bibliotheken für sich benötigt. Da es in einem CMS mit bereits zu viel JS implementiert werden muss, möchte ich das von mir eingefügte JS minimieren .

Ich möchte also wissen, ob es eine Möglichkeit gibt, Chrome dazu zu bringen, "popstate" beim Laden nicht zu starten, oder ob jemand versucht hat, History.js zu verwenden, da alle Bibliotheken zu einer Datei zusammengefasst sind.

Spliter
quelle
3
Das Verhalten von Firefox ist das spezifizierte und korrekte Verhalten. Ab Chrome 34 ist es jetzt behoben! Yay für die Opernentwickler!
Henry Chan

Antworten:

49

In Google Chrome in Version 19 funktionierte die Lösung von @spliter nicht mehr. Wie @johnnymire hervorhob, existiert history.state in Chrome 19, ist aber null.

Meine Problemumgehung besteht darin, window.history.state! == null hinzuzufügen, um zu überprüfen, ob der Status in window.history vorhanden ist:

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

Ich habe es in allen gängigen Browsern und in den Chrome-Versionen 19 und 18 getestet. Es sieht so aus, als ob es funktioniert.

Pavel Linkesch
quelle
5
Danke für die Warnung; Ich denke, Sie können das vereinfachen inund nullüberprüfen, nur != nullweil sich das auch darum kümmern wird undefined.
Pimvdb
5
Dies funktioniert nur, wenn Sie nullin Ihren history.pushState()Methoden immer wie verwenden history.pushState(null,null,'http//example.com/). Zugegeben, das würde wahrscheinlich am meisten verwendet werden (so ist es in jquery.pjax.js und den meisten anderen Demos eingerichtet). Aber wenn der Browser implementiert window.history.state(wie FF und Chrome 19+), könnte window.history.state bei einer Aktualisierung oder nach einem Neustart des Browsers ungleich Null sein
unerklärtBacn
19
In Chrome 21 funktioniert dies bei mir nicht mehr. Ich habe beim Laden der Seite nur "popped" auf "false" gesetzt und dann bei jedem Push oder Pop auf "popped" gesetzt. Dies neutralisiert den Pop von Chrome beim ersten Laden. Es wird hier etwas besser erklärt: github.com/defunkt/jquery-pjax/issues/143#issuecomment-6194330
Chad von Nau
1
@ChadvonNau ausgezeichnete Idee, und es funktioniert ein Vergnügen - vielen Dank!
sowasred2012
Ich kam mit dem gleichen Problem und beendete die Verwendung der einfachen @ ChadvonNau-Lösung.
Pherrymason
30

Falls Sie nicht für jeden Handler, den Sie zu onpopstate hinzufügen, besondere Maßnahmen ergreifen möchten, ist meine Lösung möglicherweise für Sie interessant. Ein großes Plus dieser Lösung ist auch, dass Onpopstate-Ereignisse behandelt werden können, bevor das Laden der Seite abgeschlossen ist.

Führen Sie diesen Code einfach einmal aus, bevor Sie Onpopstate-Handler hinzufügen, und alles sollte wie erwartet funktionieren (auch bekannt als 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);
})();

Wie es funktioniert:

Chrome , Safari und wahrscheinlich andere Webkit-Browser lösen das Ereignis onpopstate aus, wenn das Dokument geladen wurde. Dies ist nicht beabsichtigt, daher blockieren wir Popstate-Ereignisse bis zur ersten Ereignisschleife nach dem Laden des Dokuments. Dies erfolgt durch die Aufrufe von präventDefault und stopImmediatePropagation (im Gegensatz zu stopPropagation stoppt stopImmediatePropagation alle Aufrufe der Ereignishandler sofort).

Da der readyState des Dokuments jedoch bereits "vollständig" ist, wenn Chrome fälschlicherweise onpopstate auslöst, lassen wir opopstate-Ereignisse zu, die ausgelöst wurden, bevor das Laden des Dokuments abgeschlossen wurde, um onpopstate-Aufrufe zu ermöglichen, bevor das Dokument geladen wurde.

Update 2014-04-23: Es wurde ein Fehler behoben, durch den Popstate-Ereignisse blockiert wurden, wenn das Skript nach dem Laden der Seite ausgeführt wurde.

Torben
quelle
4
Beste Lösung bisher für mich. Ich verwende die setTimeout-Methode seit Jahren, aber sie verursacht fehlerhaftes Verhalten, wenn die Seite langsam geladen wird / wenn der Benutzer den Push-Status sehr schnell auslöst. window.history.stateschlägt beim erneuten Laden fehl, wenn ein Status festgelegt wurde. Festlegen der Variablen vor dem Code von pushState Clutters. Ihre Lösung ist elegant und kann über andere Skripte gelegt werden, ohne den Rest der Codebasis zu beeinträchtigen. Vielen Dank.
Kik
4
Das ist DIE Lösung! Wenn ich das erst vor Tagen gelesen hätte, als ich versucht habe, dieses Problem zu lösen. Dies ist die einzige, die einwandfrei funktioniert hat. Danke dir!
Ben Fleming
1
Hervorragende Erklärung und die einzige Lösung, die einen Versuch wert ist.
Sunny R Gupta
Dies ist eine großartige Lösung. Zum Zeitpunkt des Schreibens, neueste Chrom & FF gut, Problem ist mit Safari (Mac & iPhone). Diese Lösung funktioniert wie ein Zauber.
Vin
1
Woot! Wenn Safari Ihren Tag ruiniert, sind dies die Codes, nach denen Sie suchen!
DanO
28

Die Verwendung von setTimeout ist keine korrekte Lösung, da Sie nicht wissen, wie lange das Laden des Inhalts dauern wird, sodass das Popstate-Ereignis möglicherweise nach dem Timeout ausgegeben wird.

Hier ist meine Lösung: 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);
});
Sonny Piers
quelle
Dies ist die einzige Lösung, die für mich funktioniert hat. Vielen Dank!
Justin
3
Süss! Funktioniert besser als die am besten bewertete und / oder akzeptierte Antwort!
ProblemsOfSumit
Hat jemand den aktiven Link zu dieser Lösung, kann ich den angegebenen Link nicht erreichen gist.github.com/3551566
Harbir
1
Warum brauchst du das Wesentliche?
Oleg Vaskevich
Perfekte Antwort. Vielen Dank.
Sideroxylon
25

Die Lösung wurde in gefunden den Zeilen 195-225 von jquery.pjax.j :

// 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
  }
})
Spliter
quelle
8
Diese Lösung funktioniert unter Windows (Chrome 19) nicht mehr und funktioniert auch unter Mac (Chrome 18) nicht mehr. Scheint, als ob history.state in Chrome 19 existiert, aber nicht in 18.
Johnnymire
1
@ Johnnymire Ich habe meine Lösung für Chrome 19 niedriger gepostet: stackoverflow.com/a/10651028/291500
Pavel Linkesch
Jemand sollte diese Antwort bearbeiten, um dieses Verhalten nach Chrome 19 widerzuspiegeln.
Jorge Suárez de Lis
5
Für alle, die nach einer Lösung suchen, wirkt die Antwort von Torben ab heute wie ein Zauber auf aktuelle Chrome- und Firefox-Versionen
Jorge Suárez de Lis
1
Jetzt im April 2015 habe ich diese Lösung verwendet, um ein Problem mit Safari zu lösen. Ich musste das onpopstate-Ereignis verwenden, um eine Handhabung durch Drücken der Zurück-Taste in einer hybriden iOS / Android-App mit Cordova zu erreichen. Safari feuert seinen Onpopstate auch nach einem Reload ab, den ich programmgesteuert aufgerufen habe. Mit der Art und Weise, wie mein Code eingerichtet wurde, ging er nach dem Aufruf von reload () in eine Endlosschleife. Dies hat es behoben. Ich brauchte nur die ersten 3 Zeilen nach der Ereignisdeklaration (plus die Zuweisungen oben).
Martavis P.
14

Eine direktere Lösung als die Neuimplementierung von pjax besteht darin, eine Variable in pushState festzulegen und in popState nach der Variablen zu suchen, damit der anfängliche popState beim Laden nicht inkonsistent ausgelöst wird (keine jquery-spezifische Lösung, die nur für Ereignisse verwendet wird):

$(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
}
Tom McKenzie
quelle
Wäre das nicht, wenn immer falsch bewertet würde? Ich meine, window.history.readyist immer wahr.
Andriy Drozdyuk
Das Beispiel wurde aktualisiert, um etwas klarer zu sein. Grundsätzlich möchten Sie Popstate-Ereignisse nur dann behandeln, wenn Sie Entwarnung geben. Sie könnten den gleichen Effekt mit einem setTimeout 500 ms nach dem Laden erzielen, aber um ehrlich zu sein, ist es ein bisschen billig.
Tom McKenzie
3
Dies sollte die Antwort IMO sein, ich habe Pavels Lösung ausprobiert und es hat in Firefox
Porco
Dies war für mich eine einfache Lösung für dieses nervige Problem. Vielen Dank!
Nirazul
!window.history.ready && !ev.originalEvent.state- Diese beiden Dinge werden nie definiert, wenn ich die Seite aktualisiere. Daher wird kein Code ausgeführt.
Chovy
4

Dem ersten onpopstate- Ereignis von Webkit ist kein Status zugewiesen. Sie können dies also verwenden, um nach unerwünschtem Verhalten zu suchen :

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

Eine umfassende Lösung, die die Navigation zurück zur ursprünglichen Seite ermöglicht, würde auf dieser Idee aufbauen:

<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>

Während dies immer noch zweimal ausgelöst werden könnte (mit einer raffinierten Navigation), kann es einfach mit einem Check gegen die vorherige href behandelt werden.

so M
quelle
1
Vielen Dank. Die pjax-Lösung funktioniert unter iOS 5.0 nicht mehr, da dies window.history.stateauch unter iOS null ist. Es reicht aus, nur zu überprüfen, ob die Eigenschaft e.state null ist.
Husky
Gibt es bekannte Probleme mit dieser Lösung? Es funktioniert perfekt auf allen Browsern, die ich teste.
Jacob Ewing
3

Dies ist meine Problemumgehung.

window.setTimeout(function() {
  window.addEventListener('popstate', function() {
    // ...
  });
}, 1000);
Baggz
quelle
Nicht für mich auf Chromium 28 unter Ubuntu. Manchmal wird das Ereignis immer noch ausgelöst.
Jorge Suárez de Lis
Versuchte das selbst, funktioniert nicht immer. Manchmal feuert der Popstate später als das Timeout, abhängig vom anfänglichen Laden der Seite
Andrew Burgess
1

Hier ist meine Lösung:

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)');
        }
    }
})   
Programmierer
quelle
0

Der beste Weg, um Chrome dazu zu bringen, Popstate beim Laden einer Seite nicht auszulösen, besteht darin, https://code.google.com/p/chromium/issues/detail?id=63040 hochzustimmen . Sie wissen, dass Chrome seit zwei Jahren nicht mehr mit der HTML5-Spezifikation übereinstimmt und haben es immer noch nicht behoben!

Dave
quelle
2
Dies wurde seit Chrome 34
behoben.
0

Im Falle einer Verwendung event.state !== nullfunktioniert die Rückkehr zum Verlauf zur zuerst geladenen Seite in nicht mobilen Browsern nicht. Ich benutze sessionStorage, um zu markieren, wann die Ajax-Navigation wirklich beginnt.

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);

Ilusionist
quelle
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', Funktion (e) {if (e.state! == null) {location.href = e.state;}}, false);
Ilusionist
-1

Die vorgestellten Lösungen haben ein Problem beim erneuten Laden der Seite. Folgendes scheint besser zu funktionieren, aber ich habe nur Firefox und Chrome getestet. Es nutzt die Aktualität, dass es einen Unterschied zwischen e.event.stateund zu geben scheint window.history.state.

window.addEvent('popstate', function(e) {
    if(e.event.state) {
        window.location.reload(); // Event code
    }
});
user2604836
quelle
e.eventist undefiniert
chovy
-1

Ich weiß, dass Sie dagegen gefragt haben, aber Sie sollten wirklich nur History.js verwenden, da es eine Million Browser-Inkompatibilitäten beseitigt. Ich bin nur die manuelle Reparaturroute gegangen, um später festzustellen, dass es immer mehr Probleme gab, die Sie erst später herausfinden werden. Es ist heutzutage wirklich nicht so schwer:

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

Und lesen Sie die API unter https://github.com/browserstate/history.js

ubershmekel
quelle
-1

Dies löste das Problem für mich. Ich habe lediglich eine Timeout-Funktion festgelegt, die die Ausführung der Funktion so lange verzögert, dass das Popstate-Ereignis, das beim Laden der Seite ausgelöst wird, übersehen wird

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

Sie können ein Ereignis erstellen und es nach Ihrem Onload-Handler auslösen.

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

Beachten Sie, dass dies in Chrome / Safari etwas kaputt ist, aber ich habe den Patch bei WebKit eingereicht und er sollte bald verfügbar sein, aber es ist der "korrekteste" Weg.

Kinlan
quelle
-2

Dies funktionierte bei mir in Firefox und Chrome

window.onpopstate = function(event) { //back button click
    console.log("onpopstate");
    if (event.state) {
        window.location.reload();
    }
};
Alexey
quelle