Come rilevare se l'URL è cambiato dopo l'hash in JavaScript


160

Come posso verificare se un URL è cambiato in JavaScript? Ad esempio, siti Web come GitHub, che utilizzano AJAX, aggiungeranno le informazioni della pagina dopo un simbolo # per creare un URL univoco senza ricaricare la pagina. Qual è il modo migliore per rilevare se questo URL cambia?

  • L' onloadevento viene richiamato di nuovo?
  • Esiste un gestore eventi per l'URL?
  • O l'URL deve essere controllato ogni secondo per rilevare una modifica?

1
Per i futuri visitatori, una nuova risposta da @aljgom dal 2018 è la soluzione migliore: stackoverflow.com/a/52809105/151503
Redzarf

Risposte:


106

Nei browser moderni (IE8 +, FF3.6 +, Chrome), puoi semplicemente ascoltare l' hashchangeevento window.

In alcuni vecchi browser, è necessario un timer che controlli continuamente location.hash. Se stai usando jQuery, c'è un plugin che fa esattamente questo.


132
Questo, a quanto ho capito, funziona solo per la modifica della parte dopo il segno # (da cui il nome dell'evento)? E non per la modifica completa dell'URL, come sembra implicito dal titolo della domanda.
NPC,

13
@NPC Qualche gestore per la modifica completa dell'URL (senza tag di ancoraggio)?
Neha Choudhary,

Raramente sono necessari eventi di timeout: utilizzare gli eventi mouse e tastiera per il controllo.
Sjeiti,

cosa succede se il percorso cambia, non l'hash?
SuperUberDuper il

@SuperUberDuper Se il percorso cambia perché l'utente ha avviato una navigazione / fatto clic su un collegamento ecc., Verrà visualizzato solo un beforeunloadevento. Se il tuo codice ha avviato la modifica dell'URL, lo sa meglio.
phihag

92

Volevo poter aggiungere locationchangelistener di eventi. Dopo la modifica di seguito, saremo in grado di farlo, in questo modo

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

Al contrario, window.addEventListener('hashchange',()=>{})sparerebbe solo se la parte dopo un hashtag in un url cambia e window.addEventListener('popstate',()=>{})non funziona sempre.

Questa modifica, simile alla risposta di Christian , modifica l'oggetto della storia per aggiungere alcune funzionalità.

Per impostazione predefinita, c'è un popstateevento, ma non ci sono eventi per pushstatee replacestate.

Questo modifica queste tre funzioni in modo che tutti generino un locationchangeevento personalizzato da usare, e anche pushstateed replacestateeventi se si desidera utilizzare quelli:

/* 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'))
});

2
Ottimo, quello di cui avevo bisogno Grazie
eslamb il

Grazie, davvero utile!
Thomas Lang,

1
C'è un modo per farlo in IE? Poiché non supporta =>
joshuascotton,

1
@joshuacotton => è una funzione freccia, è possibile sostituire f => funzione fname () {...} con la funzione (f) {return function fname () {...}}
aljgom

1
Questo è molto utile e funziona come un fascino. Questa dovrebbe essere la risposta accettata.
Kunal Parekh,

77

usa questo codice

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

con jQuery

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

7
Questa deve essere contrassegnata come risposta. È una riga, utilizza il modello di eventi del browser e non si basa su infiniti timeout che consumano risorse
Nick Mitchell,

1
Questa è secondo me la migliore risposta qui. Nessun plug-in e codice minimo
agDev

14
Non funziona su modifiche
all'URL

26
In che modo questa è la risposta migliore se viene attivata solo quando è presente un hash nell'URL?
Aaron,

1
Dovresti usare addEventListener invece di sostituire direttamente il valore onhashchange, nel caso in cui anche qualcos'altro voglia ascoltare.
Broken-e

55

MODIFICA dopo un po 'di ricerche:

Sembra in qualche modo che sono stato ingannato dalla documentazione presente sui documenti di Mozilla. L' popstateevento (e la sua funzione di callback onpopstate) non vengono attivati ogni volta che pushState()o replaceState()vengono chiamati nel codice. Pertanto la risposta originale non si applica in tutti i casi.

Tuttavia c'è un modo per aggirare questo problema tramite l'applicazione di patch di scimmia alle funzioni secondo @ alpha123 :

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

Risposta originale

Dato che il titolo di questa domanda è " Come rilevare la modifica dell'URL ", la risposta, quando vuoi sapere quando cambia il percorso completo (e non solo l'ancora hash), è che puoi ascoltare l' popstateevento:

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

Riferimento per popstate in Mozilla Docs

Attualmente (gennaio 2017) esiste il supporto per popstate dal 92% dei browser in tutto il mondo.


11
Incredibile che dobbiamo ancora ricorrere a tali hack nel 2018.
Capra

1
Questo ha funzionato per il mio caso d'uso - ma proprio come dice @goat - è incredibile che non ci sia supporto nativo per questo ...
wasddd_

1
quali argomenti? come installerei fireEvents?
SeanMC,

2
Si noti che questo è anche inaffidabile in molti casi. Ad esempio, non rileverà la modifica dell'URL quando fai clic su diverse varianti del prodotto Amazon (i riquadri sotto il prezzo).
thdoan

2
questo non rileverà un cambiamento da localhost / foo a localhost / baa se non si utilizza location.back ()
SuperUberDuper

53

Con jquery (e un plug-in) puoi farlo

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

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

Altrimenti sì, dovresti usare setInterval e verificare la presenza di un evento hash (window.location.hash)

Aggiornare! Una semplice bozza

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

posso rilevare il cambio di (window.location) e gestirlo? (senza jquery)
BergP,

6
Puoi @BergP, usando il semplice listener javascript: window.addEventListener("hashchange", hashChanged);
Ron

1
L'intervallo di tempo così breve è buono per l'app? Cioè, non mantiene il browser troppo occupato nell'esecuzione della detect()funzione?
Hasib Mahmud,

4
@HasibMahmud, quel codice sta eseguendo 1 controllo di uguaglianza ogni 100 ms. Ho appena verificato nel mio browser che posso fare 500 controlli di uguaglianza in meno di 1 ms. Quindi quel codice usa 1/50000 della mia potenza di elaborazione. Non mi preoccuperei troppo.
z5h,

24

Aggiungi un listener di eventi con modifica dell'hash!

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

Oppure, per ascoltare tutte le modifiche all'URL:

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

Questo è meglio di qualcosa come il codice qui sotto perché solo una cosa può esistere in window.onhashchange e probabilmente sovrascriverai il codice di qualcun altro.

// Bad code example

window.onhashchange = function() { 
     // Code that overwrites whatever was previously in window.onhashchange  
}

1
pop state si innesca solo quando si pop uno stato, non si spinge uno
SeanMC

8

questa soluzione ha funzionato per me:

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

18
Questo è un metodo piuttosto rudimentale, penso che possiamo mirare più in alto.
Carles Alcolea,

8
Anche se sono d'accordo con @CarlesAlcolea che questo sembra vecchio, nella mia esperienza è ancora l'unico modo per cogliere il 100% di tutte le modifiche all'URL.
Trev14,

5
@ahofmann suggerisce (in una modifica che avrebbe dovuto essere un commento) di cambiare setIntervalin setTimeout: "l'uso di setInterval () farà arrestare il browser dopo un po ', perché creerà una nuova chiamata a checkURLchange () ogni secondo. setTimeout () è la solizione corretta, perché viene chiamata una sola volta. "
divibisan

Questa è stata l'unica soluzione per rilevare tutte le modifiche all'URL, ma il consumo di risorse mi sta costringendo a cercare altre soluzioni.
RitornoTabella

3
O invece di usare setTimeoutcome suggerisce @divibisan, sposta l' setIntervalesterno della funzione. checkURLchange();diventa anche facoltativo.
Arististfl

4

Sebbene sia una vecchia domanda, il progetto della barra della posizione è molto utile.

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

È per nodeJs, dobbiamo usare browserify per usarlo sul lato client. No?
hack4mer,

1
No non lo è. Funziona nel browser
Ray Booysen,

3

Per ascoltare le modifiche all'URL, vedi sotto:

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

Utilizzare questo stile se si intende arrestare / rimuovere il listener dopo alcune condizioni.

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

2

Mentre facevo una piccola estensione di Chrome, ho riscontrato lo stesso problema con un problema aggiuntivo: a volte, la pagina cambia ma non l'URL.

Ad esempio, vai alla home page di Facebook e fai clic sul pulsante "Home". Ricaricherai la pagina ma l'URL non cambierà (stile app di una pagina).

Il 99% delle volte, stiamo sviluppando siti Web in modo da poter ottenere quegli eventi da Frameworks come Angular, React, Vue ecc.

MA , nel mio caso di un'estensione di Chrome (in Vanilla JS), ho dovuto ascoltare un evento che si innescherebbe per ogni "cambio di pagina", che in genere può essere intercettato da un cambio di URL, ma a volte no.

La mia soluzione fatta in casa era la seguente:

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

Quindi, in sostanza, la soluzione leoneckert, applicata alla cronologia delle finestre, cambierà quando una pagina cambia in un'unica app.

Non la scienza missilistica, ma la soluzione più pulita che ho trovato, considerando che qui stiamo solo controllando una parità intera e non oggetti più grandi o l'intero DOM.


La tua soluzione è semplice e funziona molto bene con le estensioni di Chrome. Vorrei suggerire di utilizzare l'ID video di YouTube anziché la lunghezza. stackoverflow.com/a/3452617/808901
Rod Lima

2
non dovresti usare setInterval perché ogni volta che chiami hear (xy) viene creato un nuovo intervallo e finisci con migliaia di intervalli.
Stef Chäser,

1
Hai ragione, ho notato che dopo e non ho cambiato il mio post, lo modificherò. Nel tempo ho persino riscontrato un arresto anomalo di Google Chrome a causa di perdite di RAM. Grazie per il commento
Alburkerk

window.historyha una lunghezza massima di 50 (almeno a partire da Chrome 80). Successivamente, window.history.lengthrestituisce sempre 50. Quando ciò accade, questo metodo non riconoscerà eventuali modifiche.
Collin Krawll,

1

Guarda la funzione di scarico di jQuery. Gestisce tutte le cose.

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

L'evento di scarico viene inviato all'elemento della finestra quando l'utente si allontana dalla pagina. Questo potrebbe significare una delle tante cose. L'utente avrebbe potuto fare clic su un collegamento per uscire dalla pagina o digitare un nuovo URL nella barra degli indirizzi. I pulsanti avanti e indietro attiveranno l'evento. La chiusura della finestra del browser provoca l'attivazione dell'evento. Anche un ricaricamento della pagina creerà prima un evento di scaricamento.

$(window).unload(
    function(event) {
        alert("navigating");
    }
);

2
Laddove il contenuto è Ajax, l'URL può cambiare senza che la finestra venga scaricata. Questo script non rileva una modifica dell'URL, sebbene possa essere comunque utile per alcuni utenti che dispongono di una finestra di scarico su ogni modifica dell'URL.
Deborah,

come nella domanda @ranbuch, questo è specifico solo per le pagine che non sono un'applicazione a pagina singola e questo evento sta solo guardando l'evento della finestra di scarico, non la modifica dell'URL.
ncubica,

0
window.addEventListener("beforeunload", function (e) {
    // do something
}, false);

11
questo non funzionerà nel contesto delle applicazioni a pagina singola poiché l'evento di
scaricamento

0

La risposta di seguito viene da qui (con la vecchia sintassi javascript (nessuna funzione freccia, supporto 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"));
  });
})();

0

Un altro modo semplice per farlo è quello di aggiungere un evento click, tramite un nome di classe ai tag anchor sulla pagina per rilevare quando è stato fatto clic, quindi ora è possibile utilizzare window.location.href per ottenere i dati dell'URL che puoi usare per eseguire la tua richiesta Ajax sul server. Semplice e facile.


-1

Stai iniziando un nuovo setIntervalad ogni chiamata, senza cancellare quello precedente - probabilmente intendevi solo avere unsetTimeout

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.