So erkennen Sie, ob sich die URL nach dem Hash in JavaScript geändert hat

160

Wie kann ich überprüfen, ob sich eine URL in JavaScript geändert hat? Beispielsweise hängen Websites wie GitHub, die AJAX verwenden, Seiteninformationen nach einem # -Symbol an, um eine eindeutige URL zu erstellen, ohne die Seite neu zu laden. Was ist der beste Weg, um festzustellen, ob sich diese URL ändert?

  • Wird die onloadVeranstaltung erneut aufgerufen?
  • Gibt es einen Ereignishandler für die URL?
  • Oder muss die URL jede Sekunde überprüft werden, um eine Änderung festzustellen?
AJ00200
quelle
1
Für zukünftige Besucher ist eine neue Antwort von @aljgom aus dem Jahr 2018 die beste Lösung: stackoverflow.com/a/52809105/151503
Redzarf

Antworten:

106

In modernen Browsern (IE8 +, FF3.6 +, Chrome) können Sie das hashchangeEreignis einfach anhören window.

In einigen alten Browsern benötigen Sie einen Timer, der ständig überprüft location.hash. Wenn Sie jQuery verwenden, gibt es ein Plugin , das genau das tut.

Phihag
quelle
132
Soweit ich weiß, funktioniert dies nur für die Änderung des Teils nach dem # -Zeichen (daher der Ereignisname). Und nicht für eine vollständige URL-Änderung, wie der Titel der Frage zu implizieren scheint.
NPC
13
@NPC Gibt es einen Handler für die vollständige URL-Änderung (ohne Ankertag)?
Neha Choudhary
Sie benötigen selten Timeout-Ereignisse: Verwenden Sie Maus- und Tastenkombinationen zur Überprüfung.
Sjeiti
Was ist, wenn sich der Pfad ändert, nicht der Hash?
SuperUberDuper
@SuperUberDuper Wenn sich der Pfad ändert, weil der Benutzer eine Navigation initiiert / auf einen Link geklickt hat usw., wird nur ein beforeunloadEreignis angezeigt . Wenn Ihr Code die URL-Änderung initiiert hat, weiß er es am besten.
Phihag
92

Ich wollte locationchangeEreignis-Listener hinzufügen können . Nach der folgenden Änderung können wir dies folgendermaßen tun

window.addEventListener('locationchange', function(){
    console.log('location changed!');
})

Im Gegensatz dazu window.addEventListener('hashchange',()=>{})würde nur ausgelöst, wenn sich der Teil nach einem Hashtag in einer URL ändert und window.addEventListener('popstate',()=>{})nicht immer funktioniert.

Diese Änderung ändert , ähnlich wie Christians Antwort , das Verlaufsobjekt, um einige Funktionen hinzuzufügen.

Standardmäßig gibt es ein popstateEreignis, aber keine Ereignisse für pushstateund replacestate.

Dadurch werden diese drei Funktionen so geändert, dass alle ein benutzerdefiniertes locationchangeEreignis auslösen, das Sie verwenden können, pushstatesowie replacestateEreignisse, wenn Sie diese verwenden möchten:

/* These are the modifications: */
history.pushState = ( f => function pushState(){
    var ret = f.apply(this, arguments);
    window.dispatchEvent(new Event('pushstate'));
    window.dispatchEvent(new Event('locationchange'));
    return ret;
})(history.pushState);

history.replaceState = ( f => function replaceState(){
    var ret = f.apply(this, arguments);
    window.dispatchEvent(new Event('replacestate'));
    window.dispatchEvent(new Event('locationchange'));
    return ret;
})(history.replaceState);

window.addEventListener('popstate',()=>{
    window.dispatchEvent(new Event('locationchange'))
});
aljgom
quelle
2
Großartig, was ich brauchte Danke
eslamb
Danke, sehr hilfreich!
Thomas Lang
1
Gibt es eine Möglichkeit, dies im IE zu tun? Da es nicht unterstützt =>
Joshuascotton
1
@joshuacotton => ist eine Pfeilfunktion, Sie können f => Funktion fname () {...} durch Funktion (f) {Rückgabefunktion fname () {...}}
ersetzen
1
Dies ist sehr hilfreich und wirkt wie ein Zauber. Dies sollte die akzeptierte Antwort sein.
Kunal Parekh
77

Verwenden Sie diesen Code

window.onhashchange = function() { 
     //code  
}

mit jQuery

$(window).bind('hashchange', function() {
     //code
});
Behnam Mohammadi
quelle
7
Dies muss als Antwort markiert werden. Es ist eine Zeile, verwendet das Browser-Ereignismodell und ist nicht auf endlose ressourcenintensive Zeitüberschreitungen angewiesen
Nick Mitchell,
1
Dies ist meiner Meinung nach die beste Antwort hier. Kein Plugin und minimaler Code
agDev
14
Funktioniert nicht bei Nicht-Hash-URL-Änderungen, die sehr beliebt zu sein scheinen, wie die von Slack implementierte
NycCompSci
26
Wie ist dies die beste Antwort, wenn dies nur ausgelöst wird, wenn die URL einen Hash enthält?
Aaron
1
Sie sollten addEventListener verwenden, anstatt den onhashchange-Wert direkt zu ersetzen, falls etwas anderes ebenfalls zuhören möchte.
gebrochen
55

BEARBEITEN nach ein wenig Recherche:

Es scheint irgendwie, dass ich mich von der Dokumentation in Mozilla-Dokumenten täuschen lassen habe. Das popstateEreignis (und seine Rückruffunktion onpopstate) werden nicht ausgelöst, wenn das pushState()oder replaceState()im Code aufgerufen wird. Daher gilt die ursprüngliche Antwort nicht in allen Fällen.

Es gibt jedoch eine Möglichkeit, dies zu umgehen, indem die Funktionen gemäß @ alpha123 durch Affen gepatcht werden :

var pushState = history.pushState;
history.pushState = function () {
    pushState.apply(history, arguments);
    fireEvents('pushState', arguments);  // Some event-handling function
};

Ursprüngliche Antwort

Da der Titel dieser Frage " So erkennen Sie URL-Änderungen " lautet, lautet die Antwort, wenn Sie wissen möchten, wann sich der vollständige Pfad ändert (und nicht nur der Hash-Anker), dass Sie auf das popstateEreignis warten können :

window.onpopstate = function(event) {
  console.log("location: " + document.location + ", state: " + JSON.stringify(event.state));
};

Referenz für Popstate in Mozilla Docs

Derzeit (Januar 2017) wird Popstate von 92% der Browser weltweit unterstützt.

Cristian
quelle
11
Unglaublich, dass wir 2018 noch auf solche Hacks zurückgreifen müssen.
Ziege
1
Dies funktionierte für meinen Anwendungsfall - aber genau wie @goat sagt - es ist unglaublich, dass es keine native Unterstützung dafür gibt ...
wasddd_
1
Welche Argumente? Wie würde ich fireEvents einrichten?
SeanMC
2
Beachten Sie, dass dies in vielen Fällen auch unzuverlässig ist. Beispielsweise wird die URL-Änderung nicht erkannt, wenn Sie auf verschiedene Amazon-Produktvarianten klicken (die Kacheln unter dem Preis).
Dooan
2
Dies wird keine Änderung von localhost / foo zu localhost / baa erkennen, wenn nicht location.back ()
SuperUberDuper
53

Mit jquery (und einem Plug-In) können Sie dies tun

$(window).bind('hashchange', function() {
 /* things */
});

http://benalman.com/projects/jquery-hashchange-plugin/

Andernfalls müssten Sie setInterval verwenden und nach einer Änderung des Hash-Ereignisses suchen (window.location.hash).

Aktualisieren! Ein einfacher Entwurf

function hashHandler(){
    this.oldHash = window.location.hash;
    this.Check;

    var that = this;
    var detect = function(){
        if(that.oldHash!=window.location.hash){
            alert("HASH CHANGED - new has" + window.location.hash);
            that.oldHash = window.location.hash;
        }
    };
    this.Check = setInterval(function(){ detect() }, 100);
}

var hashDetection = new hashHandler();
Pantelis
quelle
Kann ich eine Änderung von (window.location) erkennen und damit umgehen? (ohne Abfrage)
BergP
6
Sie können @BergP, mit dem einfachen Javascript-Listener: window.addEventListener("hashchange", hashChanged);
Ron
1
Ist ein so kurzes Zeitintervall gut für die App? Das heißt, hält es den Browser nicht zu beschäftigt, detect()Funktionen auszuführen ?
Hasib Mahmud
4
@ HasibMahmud, dieser Code führt alle 100 ms eine Gleichheitsprüfung durch. Ich habe gerade in meinem Browser ein Benchmarking durchgeführt, um 500 Gleichstellungsprüfungen in weniger als 1 ms durchzuführen. Dieser Code verbraucht also 1/50000 meiner Verarbeitungsleistung. Ich würde mir nicht zu viele Sorgen machen.
z5h
24

Fügen Sie einen Listener für Hash-Änderungsereignisse hinzu!

window.addEventListener('hashchange', function(e){console.log('hash changed')});

Oder um alle URL-Änderungen abzuhören:

window.addEventListener('popstate', function(e){console.log('url changed')});

Dies ist besser als der folgende Code, da in window.onhashchange nur eines vorhanden sein kann und Sie möglicherweise den Code eines anderen überschreiben.

// Bad code example

window.onhashchange = function() { 
     // Code that overwrites whatever was previously in window.onhashchange  
}
Trev14
quelle
1
Pop-Status wird nur
ausgelöst,
8

Diese Lösung hat bei mir funktioniert:

var oldURL = "";
var currentURL = window.location.href;
function checkURLchange(currentURL){
    if(currentURL != oldURL){
        alert("url changed!");
        oldURL = currentURL;
    }

    oldURL = window.location.href;
    setInterval(function() {
        checkURLchange(window.location.href);
    }, 1000);
}

checkURLchange();
leoneckert
quelle
18
Dies ist eine eher rudimentäre Methode, ich denke wir können höher zielen.
Carles Alcolea
8
Obwohl ich @CarlesAlcolea zustimme, dass sich dies alt anfühlt, ist es meiner Erfahrung nach immer noch die einzige Möglichkeit, 100% aller URL-Änderungen zu erfassen.
Trev14
5
@ahofmann schlägt vor (in einer Bearbeitung , die einen Kommentar sein sollte) Wechsel setIntervalan setTimeout. „mit setInterval () wird den Browser zum Stillstand nach einer Weile bringen, weil es einen neuen Anruf zu checkURLchange schaffen () jedem zweiten setTimeout () ist die richtige Lösung, weil sie nur einmal aufgerufen wird. "
Divibisan
Dies war die einzige Lösung, um alle URL-Änderungen zu erfassen, aber der Ressourcenverbrauch zwingt mich, nach anderen Lösungen zu suchen.
ReturnTable
3
Oder anstatt setTimeoutwie von @divibisan vorgeschlagen zu verwenden, verschieben Sie die setIntervalAußenseite der Funktion. checkURLchange();wird auch optional.
Aristidesfl
4

Obwohl dies eine alte Frage ist, ist das Location-Bar- Projekt sehr nützlich.

var LocationBar = require("location-bar");
var locationBar = new LocationBar();

// listen to all changes to the location bar
locationBar.onChange(function (path) {
  console.log("the current url is", path);
});

// listen to a specific change to location bar
// e.g. Backbone builds on top of this method to implement
// it's simple parametrized Backbone.Router
locationBar.route(/some\-regex/, function () {
  // only called when the current url matches the regex
});

locationBar.start({
  pushState: true
});

// update the address bar and add a new entry in browsers history
locationBar.update("/some/url?param=123");

// update the address bar but don't add the entry in history
locationBar.update("/some/url", {replace: true});

// update the address bar and call the `change` callback
locationBar.update("/some/url", {trigger: true});
Ray Booysen
quelle
Es ist für NodeJs, wir müssen browserify verwenden, um es clientseitig zu verwenden. Nicht wir?
Hack4mer
1
Nein, ist es nicht. Funktioniert im Browser
Ray Booysen
3

Informationen zum Anhören von URL-Änderungen finden Sie unten:

window.onpopstate = function(event) {
  console.log("location: " + document.location + ", state: " + JSON.stringify(event.state));
};

Verwenden Sie diesen Stil, wenn Sie den Listener nach einer bestimmten Bedingung stoppen / entfernen möchten.

window.addEventListener('popstate', function(e) {
   console.log('url changed')
});
Codejockie
quelle
2

Bei einer kleinen Chrome-Erweiterung hatte ich das gleiche Problem mit einem zusätzlichen Problem: Manchmal ändert sich die Seite, aber nicht die URL.

Gehen Sie zum Beispiel einfach zur Facebook-Homepage und klicken Sie auf die Schaltfläche "Home". Sie werden die Seite neu laden, aber die URL ändert sich nicht (einseitiger App-Stil).

In 99% der Fälle entwickeln wir Websites, damit wir diese Ereignisse von Frameworks wie Angular, React, Vue usw. abrufen können.

ABER in meinem Fall einer Chrome-Erweiterung (in Vanilla JS) musste ich mir ein Ereignis anhören, das bei jedem "Seitenwechsel" ausgelöst wird, der im Allgemeinen durch eine geänderte URL abgefangen werden kann, aber manchmal nicht.

Meine hausgemachte Lösung war die folgende:

listen(window.history.length);
var oldLength = -1;
function listen(currentLength) {
  if (currentLength != oldLength) {
    // Do your stuff here
  }

  oldLength = window.history.length;
  setTimeout(function () {
    listen(window.history.length);
  }, 1000);
}

Im Grunde genommen also die Leoneckert-Lösung, die auf den Fensterverlauf angewendet wird und sich ändert, wenn sich eine Seite in einer einzelnen Seiten-App ändert.

Keine Raketenwissenschaft, aber die sauberste Lösung, die ich gefunden habe, wenn man bedenkt, dass wir hier nur eine ganzzahlige Gleichheit prüfen und nicht größere Objekte oder das gesamte DOM.

Alburkerk
quelle
Ihre Lösung ist einfach und funktioniert sehr gut für Chrome-Erweiterungen. Ich möchte vorschlagen, die YouTube-Video-ID anstelle der Länge zu verwenden. stackoverflow.com/a/3452617/808901
Rod Lima
2
Sie sollten setInterval nicht verwenden, da jedes Mal, wenn Sie listen (xy) aufrufen, ein neues Intervall erstellt wird und Sie am Ende Tausende von Intervallen haben.
Stef Chäser
1
Sie haben Recht, ich habe bemerkt, dass ich meinen Beitrag nach und ohne Änderung bearbeiten werde. Damals stieß ich sogar auf einen Absturz von Google Chrome aufgrund von RAM-Lecks. Vielen Dank für den Kommentar
Alburkerk
window.historyhat eine maximale Länge von 50 (mindestens ab Chrome 80). Nach diesem Punkt wird window.history.lengthimmer 50 zurückgegeben. In diesem Fall erkennt diese Methode keine Änderungen.
Collin Krawll
1

Schauen Sie sich die jQuery-Entladefunktion an. Es kümmert sich um alle Dinge.

https://api.jquery.com/unload/

Das Entladeereignis wird an das Fensterelement gesendet, wenn der Benutzer von der Seite weg navigiert. Dies könnte eines von vielen Dingen bedeuten. Der Benutzer hätte auf einen Link klicken können, um die Seite zu verlassen, oder eine neue URL in die Adressleiste eingeben können. Die Vorwärts- und Zurück-Tasten lösen das Ereignis aus. Durch Schließen des Browserfensters wird das Ereignis ausgelöst. Sogar ein erneutes Laden der Seite führt zuerst zu einem Entladeereignis.

$(window).unload(
    function(event) {
        alert("navigating");
    }
);
Kostiantyn
quelle
2
Wenn Inhalte in Ajaxed enthalten sind, kann sich die URL ändern, ohne dass das Fenster entladen wird. Dieses Skript erkennt keine URL-Änderung, obwohl es für einige Benutzer hilfreich sein kann, die bei jeder URL-Änderung ein Fenster entladen haben.
Deborah
Wie bei der Frage @ranbuch gilt dies nur für Seiten, die keine Einzelseitenanwendung sind, und dieses Ereignis überwacht nur das Entladefensterereignis, nicht die URL-Änderung.
Ncubica
0
window.addEventListener("beforeunload", function (e) {
    // do something
}, false);
Ranbuch
quelle
11
Dies funktioniert nicht im Kontext von Anwendungen mit nur einer Seite, da das
Entladeereignis
0

Die folgende Antwort stammt von hier (mit alter Javascript-Syntax (keine Pfeilfunktion, unterstützt IE 10+)): https://stackoverflow.com/a/52809105/9168962

(function() {
  if (typeof window.CustomEvent === "function") return false; // If not IE
  function CustomEvent(event, params) {
    params = params || {bubbles: false, cancelable: false, detail: null};
    var evt = document.createEvent("CustomEvent");
    evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
    return evt;
  }
  window.CustomEvent = CustomEvent;
})();

(function() {
  history.pushState = function (f) {
    return function pushState() {
      var ret = f.apply(this, arguments);
      window.dispatchEvent(new CustomEvent("pushState"));
      window.dispatchEvent(new CustomEvent("locationchange"));
      return ret;
    };
  }(history.pushState);
  history.replaceState = function (f) {
    return function replaceState() {
      var ret = f.apply(this, arguments);
      window.dispatchEvent(new CustomEvent("replaceState"));
      window.dispatchEvent(new CustomEvent("locationchange"));
      return ret;
    };
  }(history.replaceState);
  window.addEventListener("popstate", function() {
    window.dispatchEvent(new CustomEvent("locationchange"));
  });
})();
Yao Bao
quelle
0

Eine andere einfache Möglichkeit, dies zu tun, besteht darin, ein Klickereignis über einen Klassennamen zu den Ankertags auf der Seite hinzuzufügen, um zu erkennen, wann darauf geklickt wurde. Anschließend können Sie die Datei window.location.href verwenden, um die URL-Daten abzurufen, die Sie können damit Ihre Ajax-Anfrage an den Server ausführen. Simpel und einfach.

Benjamin
quelle
-1

Sie starten setIntervalbei jedem Anruf einen neuen , ohne den vorherigen abzubrechen - wahrscheinlich wollten Sie nur einen habensetTimeout

Angelos Pikoulas
quelle