Comment détecter si l'URL a changé après le hachage en JavaScript

160

Comment puis-je vérifier si une URL a changé en JavaScript? Par exemple, des sites Web comme GitHub, qui utilisent AJAX, ajouteront des informations de page après un symbole # pour créer une URL unique sans recharger la page. Quelle est la meilleure façon de détecter si cette URL change?

  • L' onloadévénement est-il rappelé?
  • Existe-t-il un gestionnaire d'événements pour l'URL?
  • Ou l'URL doit-elle être vérifiée toutes les secondes pour détecter un changement?
AJ00200
la source
1
Pour les futurs visiteurs, une nouvelle réponse de @aljgom de 2018 est la meilleure solution: stackoverflow.com/a/52809105/151503
Redzarf

Réponses:

106

Dans les navigateurs modernes (IE8 +, FF3.6 +, Chrome), vous pouvez simplement écouter l' hashchangeévénement sur window.

Dans certains anciens navigateurs, vous avez besoin d'une minuterie qui vérifie en permanence location.hash. Si vous utilisez jQuery, il existe un plugin qui fait exactement cela.

Phihag
la source
132
Cela, si je comprends bien, ne fonctionne que pour le changement de la pièce après le signe # (d'où le nom de l'événement)? Et pas pour un changement d'URL complet, comme semble le laisser entendre le titre de la question.
PNJ
13
@NPC Un gestionnaire pour le changement complet d'URL (sans balise d'ancrage)?
Neha Choudhary
Vous avez rarement besoin d'événements de timeout: utilisez les événements de la souris et du clavier pour vérifier.
Sjeiti
et si le chemin change, pas le hachage?
SuperUberDuper
@SuperUberDuper Si le chemin change parce que l'utilisateur a lancé une navigation / cliqué sur un lien etc., alors vous ne verrez qu'un beforeunloadévénement. Si votre code a initié le changement d'URL, il le sait mieux.
phihag
92

Je voulais pouvoir ajouter des locationchangeécouteurs d'événements. Après la modification ci-dessous, nous pourrons le faire, comme ceci

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

En revanche, window.addEventListener('hashchange',()=>{})ne se déclencherait que si la partie après un hashtag dans une URL change, etwindow.addEventListener('popstate',()=>{}) ne fonctionne pas toujours.

Cette modification, similaire à la réponse de Christian , modifie l'objet historique pour ajouter des fonctionnalités.

Par défaut, il y a un popstateévénement, mais il n'y a aucun événement pour pushstate, et replacestate.

Cela modifie ces trois fonctions afin que toutes déclenchent un locationchangeévénement personnalisé que vous pouvez utiliser, ainsi pushstateque des replacestateévénements si vous souhaitez les utiliser:

/* 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
la source
2
Super, ce dont j'avais besoin Merci
eslamb
Merci, vraiment utile!
Thomas Lang
1
Existe-t-il un moyen de le faire dans IE? Comme il ne prend pas en charge =>
joshuascotton
1
@joshuacotton => est une fonction fléchée, vous pouvez remplacer f => function fname () {...} par function (f) {return function fname () {...}}
aljgom
1
Ceci est très utile et fonctionne comme un charme. Cela devrait être la réponse acceptée.
Kunal Parekh
77

utiliser ce code

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

avec jQuery

$(window).bind('hashchange', function() {
     //code
});
Behnam Mohammadi
la source
7
Cela doit être marqué comme la réponse. C'est une ligne, utilise le modèle d'événement du navigateur et ne repose pas sur des délais infinis consommant des ressources
Nick Mitchell
1
C'est à mon avis la meilleure réponse ici. Pas de plugin et code minimal
agDev
14
Ne fonctionne pas sur les changements d'URL non hachés qui semblent être très populaires tels que celui implémenté par Slack
NycCompSci
26
Quelle est la meilleure réponse si cela n'est déclenché que lorsqu'il y a un hachage dans l'URL?
Aaron
1
Vous devriez utiliser addEventListener au lieu de remplacer directement la valeur onhashchange, au cas où quelque chose d'autre voudrait également écouter.
broken-e
55

EDIT après un peu de recherche:

Il semble en quelque sorte que j'ai été trompé par la documentation présente sur Mozilla docs. L' popstateévénement (et sa fonction de rappel onpopstate) ne sont pas déclenchés chaque fois que le pushState()ou replaceState()est appelé dans le code. Par conséquent, la réponse originale ne s'applique pas dans tous les cas.

Cependant, il existe un moyen de contourner cela en appliquant des correctifs uniques aux fonctions selon @ alpha123 :

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

Réponse originale

Étant donné que le titre de cette question est " Comment détecter le changement d'URL ", la réponse, lorsque vous voulez savoir quand le chemin complet change (et pas seulement l'ancre de hachage), est que vous pouvez écouter l' popstateévénement:

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

Référence pour popstate dans Mozilla Docs

Actuellement (janvier 2017), 92% des navigateurs dans le monde prennent en charge popstate .

Cristian
la source
11
Incroyable que nous devions encore recourir à de tels hacks en 2018.
chèvre
1
Cela a fonctionné pour mon cas d'utilisation - mais tout comme @goat le dit - c'est incroyable qu'il n'y ait pas de support natif pour cela ...
wasddd_
1
quels arguments? comment configurer fireEvents?
SeanMC
2
Notez que cela est également peu fiable dans de nombreux cas. Par exemple, il ne détectera pas le changement d'URL lorsque vous cliquez sur différentes variantes de produits Amazon (les vignettes sous le prix).
thdoan
2
cela ne détectera pas de changement de localhost / foo à localhost / baa si vous n'utilisez pas location.back ()
SuperUberDuper
53

Avec jquery (et un plug-in), vous pouvez faire

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

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

Sinon, oui, vous devrez utiliser setInterval et rechercher un changement dans l'événement de hachage (window.location.hash)

Mettre à jour! Un simple brouillon

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
la source
puis-je détecter un changement de (window.location) et le gérer? (sans jquery)
BergP
6
Vous pouvez @BergP, en utilisant l'écouteur javascript simple: window.addEventListener("hashchange", hashChanged);
Ron
1
Un intervalle de temps aussi court est-il bon pour l'application? Autrement dit, cela ne maintient pas le navigateur trop occupé dans l'exécution des detect()fonctions?
Hasib Mahmud
4
@HasibMahmud, ce code effectue une vérification d'égalité toutes les 100 ms. Je viens de comparer dans mon navigateur que je peux faire 500 contrôles d'égalité en moins de 1 ms. Donc, ce code utilise 1 / 50000e de ma puissance de traitement. Je ne m'inquiéterais pas trop.
z5h
24

Ajoutez un écouteur d'événement de changement de hachage!

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

Ou, pour écouter toutes les modifications d'URL:

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

C'est mieux que quelque chose comme le code ci-dessous car une seule chose peut exister dans window.onhashchange et vous risquez d'écraser le code de quelqu'un d'autre.

// Bad code example

window.onhashchange = function() { 
     // Code that overwrites whatever was previously in window.onhashchange  
}
Trev14
la source
1
l'état pop ne se déclenche que lorsque vous pop un état, pas d'en pousser un
SeanMC
8

cette solution a fonctionné pour moi:

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();
Léoneckert
la source
18
C'est une méthode assez rudimentaire, je pense que nous pouvons viser plus haut.
Carles Alcolea
8
Bien que je sois d'accord avec @CarlesAlcolea pour dire que cela semble vieux, d'après mon expérience, c'est toujours le seul moyen de capturer 100% de tous les changements d'URL.
Trev14
5
@ahofmann suggère (dans une édition qui aurait dû être un commentaire) de changer setIntervalen setTimeout: "l'utilisation de setInterval () arrêtera le navigateur après un certain temps, car cela créera un nouvel appel à checkURLchange () toutes les secondes. setTimeout () est la bonne solution, car elle n'est appelée qu'une seule fois. "
divibisan
Cela a été la seule solution pour attraper tous les changements d'URL, mais la consommation de ressources m'oblige à rechercher d'autres solutions.
RetourTable le
3
Ou au lieu d'utiliser setTimeoutcomme @divibisan le suggère, déplacez l' setIntervalextérieur de la fonction. checkURLchange();devient également facultatif.
aristidesfl
4

Bien que vieille question, le projet de barre de localisation est très 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});
Ray Booysen
la source
C'est pour nodeJs, nous devons utiliser browserify pour l'utiliser côté client. Pas nous?
hack4mer
1
Non, ça ne l'est pas. Fonctionne dans le navigateur
Ray Booysen
3

Pour écouter les modifications d'URL, voir ci-dessous:

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

Utilisez ce style si vous avez l'intention d'arrêter / de supprimer l'auditeur après une certaine condition.

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

En faisant une petite extension chrome, j'ai rencontré le même problème avec un problème supplémentaire: Parfois, la page change mais pas l'URL.

Par exemple, allez simplement sur la page d'accueil Facebook et cliquez sur le bouton «Accueil». Vous rechargerez la page mais l'URL ne changera pas (style d'application d'une page).

99% du temps, nous développons des sites Web afin que nous puissions obtenir ces événements à partir de Frameworks tels que Angular, React, Vue, etc.

MAIS , dans mon cas d'une extension Chrome (dans Vanilla JS), j'ai dû écouter un événement qui se déclenchera à chaque "changement de page", qui peut généralement être capturé par une URL modifiée, mais parfois ce n'est pas le cas.

Ma solution maison était la suivante:

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

Donc, fondamentalement, la solution leoneckert, appliquée à l'historique des fenêtres, qui changera lorsqu'une page change dans une seule application de page.

Ce n'est pas sorcier, mais la solution la plus propre que j'ai trouvée, étant donné que nous ne vérifions qu'une égalité entière ici, et non des objets plus gros ou l'ensemble du DOM.

Alburkerk
la source
Votre solution est simple et fonctionne très bien pour les extensions Chrome. Je voudrais suggérer d'utiliser l'identifiant de la vidéo YouTube au lieu de la longueur. stackoverflow.com/a/3452617/808901
Rod Lima
2
vous ne devriez pas utiliser setInterval car chaque fois que vous appelez listen (xy), un nouvel intervalle est créé et vous vous retrouvez avec des milliers d'intervalles.
Stef Chäser
1
Vous avez raison, j'ai remarqué qu'après et je n'ai pas changé mon message, je vais le modifier. À l'époque, j'ai même rencontré un crash de Google Chrome à cause de fuites de RAM. Merci pour le commentaire
Alburkerk
window.historya une longueur maximale de 50 (au moins à partir de Chrome 80). Après ce point, window.history.lengthrenvoie toujours 50. Lorsque cela se produit, cette méthode ne reconnaîtra aucune modification.
Collin Krawll
1

Regardez la fonction de déchargement jQuery. Il gère toutes les choses.

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

L'événement de déchargement est envoyé à l'élément window lorsque l'utilisateur quitte la page. Cela pourrait signifier une des nombreuses choses. L'utilisateur peut avoir cliqué sur un lien pour quitter la page ou saisi une nouvelle URL dans la barre d'adresse. Les boutons avant et arrière déclencheront l'événement. La fermeture de la fenêtre du navigateur provoquera le déclenchement de l'événement. Même un rechargement de page créera d'abord un événement de déchargement.

$(window).unload(
    function(event) {
        alert("navigating");
    }
);
Kostiantyn
la source
2
Là où le contenu est Ajaxé, l'URL peut changer sans que la fenêtre ne soit déchargée. Ce script ne détecte pas de changement d'URL, bien qu'il puisse toujours être utile pour certains utilisateurs qui ont une fenêtre de déchargement à chaque changement d'URL.
Deborah
identique à la question @ranbuch, ceci n'est spécifique que pour les pages qui ne sont pas des applications à page unique, et cet événement ne regarde que l'événement de la fenêtre de déchargement, pas le changement d'URL.
ncubica
0
window.addEventListener("beforeunload", function (e) {
    // do something
}, false);
Ranbuch
la source
11
cela ne fonctionnera pas dans le contexte des applications d'une seule page puisque l'événement de déchargement ne se déclenchera jamais
ncubica
0

La réponse ci-dessous vient d'ici (avec l'ancienne syntaxe javascript (pas de fonction de flèche, prend en charge 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
la source
0

Une autre façon simple de le faire est d'ajouter un événement de clic, via un nom de classe aux balises d'ancrage sur la page pour détecter le moment où il a été cliqué, vous pouvez maintenant utiliser le window.location.href pour obtenir les données d'url qui vous pouvez utiliser pour exécuter votre requête ajax sur le serveur. Simple et facile.

Benjamin
la source
-1

Vous commencez un nouveau setIntervalà chaque appel, sans annuler le précédent - vous vouliez probablement seulement avoir unsetTimeout

Angelos Pikoulas
la source