Cómo detectar si la URL ha cambiado después del hash en JavaScript

160

¿Cómo puedo verificar si una URL ha cambiado en JavaScript? Por ejemplo, sitios web como GitHub, que usan AJAX, agregarán información de la página después de un símbolo # para crear una URL única sin volver a cargar la página. ¿Cuál es la mejor manera de detectar si esta URL cambia?

  • ¿Se onloadvuelve a llamar el evento?
  • ¿Hay un controlador de eventos para la URL?
  • ¿O debe comprobarse la URL cada segundo para detectar un cambio?
AJ00200
fuente
1
Para los futuros visitantes, una nueva respuesta de @aljgom de 2018 es la mejor solución: stackoverflow.com/a/52809105/151503
Redzarf

Respuestas:

106

En los navegadores modernos (IE8 +, FF3.6 +, Chrome), puede escuchar el hashchangeevento en window.

En algunos navegadores antiguos, necesita un temporizador que verifique continuamente location.hash. Si está utilizando jQuery, hay un complemento que hace exactamente eso.

phihag
fuente
132
Esto, según tengo entendido, ¿funciona solo para el cambio de la parte después del signo # (de ahí el nombre del evento)? Y no para el cambio completo de URL, como parece estar implícito en el título de la pregunta.
NPC
13
@NPC ¿Algún controlador para el cambio completo de URL (sin etiqueta de anclaje)?
Neha Choudhary
Raramente necesita eventos de tiempo de espera: use los eventos mouse y keyboar para verificar.
Sjeiti
¿Qué pasa si el camino cambia, no el hash?
SuperUberDuper
@SuperUberDuper Si la ruta cambia porque el usuario inició una navegación / hizo clic en un enlace, etc., solo verá un beforeunloadevento. Si su código inició el cambio de URL, lo sabe mejor.
phihag
92

Quería poder agregar locationchangeoyentes de eventos. Después de la modificación a continuación, podremos hacerlo, así

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

Por el contrario, window.addEventListener('hashchange',()=>{})solo se dispararía si la parte después de un hashtag en una url cambia, y window.addEventListener('popstate',()=>{})no siempre funciona.

Esta modificación, similar a la respuesta de Christian , modifica el objeto de historia para agregar alguna funcionalidad.

Por defecto, hay un popstateevento, pero no hay eventos para pushstate, y replacestate.

Esto modifica estas tres funciones, para que todo el fuego una costumbre locationchangeevento para que usted pueda usar, y también pushstatey replacestateeventos si desea utilizar los:

/* 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
fuente
2
Genial, lo que necesitaba Gracias
eslamb
Gracias, realmente útil!
Thomas Lang
1
¿Hay alguna manera de hacer esto en IE? Como no es compatible =>
joshuascotton
1
@joshuacotton => es una función de flecha, puede reemplazar f => function fname () {...} con function (f) {return function fname () {...}}
aljgom
1
Esto es muy útil y funciona de maravilla. Esta debería ser la respuesta aceptada.
Kunal Parekh
77

usa este código

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

con jQuery

$(window).bind('hashchange', function() {
     //code
});
Behnam Mohammadi
fuente
77
Esto debe ser marcado la respuesta. Es una línea, usa el modelo de eventos del navegador y no se basa en tiempos de espera que consumen muchos recursos
Nick Mitchell
1
Esta es, en mi opinión, la mejor respuesta aquí. Sin plugin y código mínimo
agDev
14
No funciona en cambios de URL no hash que parece ser muy popular, como el implementado por Slack
NycCompSci
26
¿Cómo es esta la mejor respuesta si esto solo se activa cuando hay un hash en la URL?
Aaron
1
Debería usar addEventListener en lugar de reemplazar el valor onhashchange directamente, en caso de que algo más quiera escuchar también.
roto-e
55

EDITAR después de un poco de investigación:

De alguna manera parece que me ha engañado la documentación presente en los documentos de Mozilla. El popstateevento (y su función de devolución de llamada onpopstate) se no se activan cada vez que el pushState()o replaceState()reciben el nombre en clave. Por lo tanto, la respuesta original no se aplica en todos los casos.

Sin embargo, hay una manera de evitar esto parcheando las funciones según @ alpha123 :

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

Respuesta original

Dado que el título de esta pregunta es " Cómo detectar el cambio de URL ", la respuesta, cuando desea saber cuándo cambia la ruta completa (y no solo el ancla de hash), es que puede escuchar el popstateevento:

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

Referencia para popstate en Mozilla Docs

Actualmente (enero de 2017) hay soporte para popstate del 92% de los navegadores en todo el mundo.

Cristian
fuente
11
Increíble que aún debemos recurrir a tales hacks en 2018.
cabra
1
Esto funcionó para mi caso de uso, pero al igual que @goat dice: es increíble que no haya soporte nativo para esto ...
wasddd_
1
que argumentos ¿Cómo establecería fireEvents?
SeanMC
2
Tenga en cuenta que esto tampoco es confiable en muchos casos. Por ejemplo, no detectará el cambio de URL cuando haga clic en diferentes variaciones de productos de Amazon (los mosaicos debajo del precio).
Thdoan
2
esto no detectará un cambio de localhost / foo a localhost / baa si no se usa location.back ()
SuperUberDuper
53

Con jquery (y un complemento) puedes hacer

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

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

De lo contrario, sí, tendría que usar setInterval y buscar un cambio en el evento hash (window.location.hash)

¡Actualizar! Un borrador simple

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
fuente
¿Puedo detectar el cambio de (window.location) y manejarlo? (sin jquery)
BergP
66
Puede @BergP, utilizando el oyente javascript simple: window.addEventListener("hashchange", hashChanged);
Ron
1
¿Es tan corto el intervalo de tiempo bueno para la aplicación? Es decir, ¿no mantiene el navegador demasiado ocupado ejecutando la detect()función?
Hasib Mahmud
44
@HasibMahmud, ese código está haciendo 1 verificación de igualdad cada 100 ms. Acabo de evaluar en mi navegador que puedo hacer 500 verificaciones de igualdad en menos de 1 ms. Entonces, ese código está usando 1/50000 de mi poder de procesamiento. No me preocuparía demasiado.
z5h
24

¡Agregue un detector de eventos de cambio hash!

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

O, para escuchar todos los cambios de URL:

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

Esto es mejor que algo como el código a continuación porque solo puede existir una cosa en window.onhashchange y posiblemente sobrescribirá el código de otra persona.

// Bad code example

window.onhashchange = function() { 
     // Code that overwrites whatever was previously in window.onhashchange  
}
Trev14
fuente
1
El estado emergente solo se dispara cuando
aparece
8

esta solución me funcionó:

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
fuente
18
Este es un método bastante rudimentario, creo que podemos apuntar más alto.
Carles Alcolea
8
Aunque estoy de acuerdo con @CarlesAlcolea en que esto se siente viejo, en mi experiencia sigue siendo la única forma de detectar el 100% de todos los cambios de URL.
Trev14
55
@ahofmann sugiere (en una edición que debería haber sido un comentario) cambiar setIntervala setTimeout: "el uso de setInterval () detendrá el navegador después de un tiempo, porque creará una nueva llamada a checkURLchange () cada segundo. setTimeout () es la solución correcta, porque se llama solo una vez ".
divibisan
Esta ha sido la única solución para detectar todos los cambios de URL, pero el consumo de recursos me está obligando a buscar otras soluciones.
VolverTable
3
O en lugar de usar setTimeoutcomo sugiere @divibisan, mueva el setIntervalexterior de la función. checkURLchange();También se convierte en opcional.
aristidesfl
4

Aunque es una vieja pregunta, el proyecto de la barra de ubicación es muy útil.

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
fuente
Es para nodeJs, necesitamos usar browserify para usarlo en el lado del cliente. Nosotros no?
hack4mer
1
No, no lo es. Funciona en el navegador
Ray Booysen
3

Para escuchar los cambios de URL, consulte a continuación:

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

Use este estilo si tiene la intención de detener / eliminar el oyente después de cierta condición.

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

Mientras hacía una pequeña extensión de Chrome, enfrenté el mismo problema con un problema adicional: veces, la página cambia pero no la URL.

Por ejemplo, solo vaya a la página de inicio de Facebook y haga clic en el botón 'Inicio'. Volverá a cargar la página, pero la URL no cambiará (estilo de aplicación de una página).

El 99% del tiempo, estamos desarrollando sitios web para que podamos obtener esos eventos de Frameworks como Angular, React, Vue, etc.

PERO , en mi caso de una extensión de Chrome (en Vanilla JS), tuve que escuchar un evento que se disparará para cada "cambio de página", que generalmente puede ser detectado por el cambio de URL, pero a veces no.

Mi solución casera fue la siguiente:

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

Básicamente, la solución de Leoneckert, aplicada al historial de ventanas, que cambiará cuando una página cambie en una sola aplicación de página.

No encontré la ciencia de cohetes, sino la solución más limpia, teniendo en cuenta que aquí solo estamos verificando una igualdad de enteros, y no objetos más grandes o todo el DOM.

Alburkerk
fuente
Su solución es simple y funciona muy bien para las extensiones de Chrome. Me gustaría sugerir usar la identificación de video de YouTube en lugar de la duración. stackoverflow.com/a/3452617/808901
Rod Lima
2
no debes usar setInterval porque cada vez que llamas a listen (xy) se crea un nuevo intervalo y terminas con miles de intervalos.
Stef Chäser
1
Tienes razón, noté que después y no cambié mi publicación, lo editaré. En el pasado, incluso me encontré con un bloqueo de Google Chrome debido a fugas de RAM. Gracias por el comentario
Alburkerk
window.historytiene una longitud máxima de 50 (al menos a partir de Chrome 80). Después de ese punto, window.history.lengthsiempre devuelve 50. Cuando eso sucede, este método no reconocerá ningún cambio.
Collin Krawll
1

Mire la función de descarga de jQuery. Maneja todas las cosas.

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

El evento de descarga se envía al elemento de ventana cuando el usuario se aleja de la página. Esto podría significar una de muchas cosas. El usuario podría haber hecho clic en un enlace para salir de la página o haber ingresado una nueva URL en la barra de direcciones. Los botones de avance y retroceso activarán el evento. Cerrar la ventana del navegador hará que se active el evento. Incluso una recarga de página creará primero un evento de descarga.

$(window).unload(
    function(event) {
        alert("navigating");
    }
);
Kostiantyn
fuente
2
Cuando el contenido está Ajaxed, la URL puede cambiar sin descargar la ventana. Este script no detecta un cambio de URL, aunque aún puede ser útil para algunos usuarios que tienen una descarga de ventana en cada cambio de URL.
Deborah
Al igual que la pregunta de @ranbuch, esto es específico solo para páginas que no son aplicaciones de una sola página, y este evento solo está mirando el evento de la ventana de descarga, no el cambio de URL.
ncubica
0
window.addEventListener("beforeunload", function (e) {
    // do something
}, false);
ranbuch
fuente
11
esto no funcionará en el contexto de las aplicaciones de una sola página, ya que el evento de descarga nunca se activará
ncubica
0

La respuesta a continuación viene de aquí (con la sintaxis antigua de JavaScript (sin función de flecha, compatible con 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
fuente
0

Otra forma sencilla de hacerlo es agregando un evento de clic, a través de un nombre de clase a las etiquetas de anclaje en la página para detectar cuándo se ha hecho clic, y ahora puede usar window.location.href para obtener los datos de URL que puede usar para ejecutar su solicitud ajax al servidor. Simple y fácil.

Benjamín
fuente
-1

Está comenzando una nueva setIntervalen cada llamada, sin cancelar la anterior, probablemente solo quería tener unsetTimeout

Angelos Pikoulas
fuente