Como detectar se o URL mudou após o hash em JavaScript

160

Como posso verificar se um URL foi alterado em JavaScript? Por exemplo, sites como o GitHub, que usam AJAX, acrescentam informações da página após um símbolo # para criar um URL exclusivo sem recarregar a página. Qual é a melhor maneira de detectar se esse URL é alterado?

  • O onloadevento é chamado novamente?
  • Existe um manipulador de eventos para o URL?
  • Ou o URL deve ser verificado a cada segundo para detectar uma alteração?
AJ00200
fonte
1
Para os futuros visitantes, uma nova resposta por @aljgom a partir de 2018 é a melhor solução: stackoverflow.com/a/52809105/151503
Redzarf

Respostas:

106

Nos navegadores modernos (IE8 +, FF3.6 +, Chrome), você pode apenas ouvir o hashchangeevento window.

Em alguns navegadores antigos, você precisa de um timer que verifique continuamente location.hash. Se você estiver usando jQuery, existe um plugin que faz exatamente isso.

phihag
fonte
132
Pelo que entendi, isso funciona apenas para a alteração da parte após o sinal # (daí o nome do evento)? E não para a alteração completa do URL, como parece estar implícito no título da pergunta.
NPC
13
@NPC Algum manipulador para alteração completa de URL (sem tag de âncora)?
Neha Choudhary
Você raramente precisa de eventos de tempo limite: use os eventos de mouse e teclado para verificar.
Sjeiti 24/07/19
e se o caminho mudar, não o hash?
SuperUberDuper
@SuperUberDuper Se o caminho for alterado porque o usuário iniciou uma navegação / clicou em um link etc., você verá apenas um beforeunloadevento. Se o seu código iniciou a alteração do URL, ele sabe melhor.
Phihag 14/12/19
92

Eu queria poder adicionar locationchangeouvintes de eventos. Após a modificação abaixo, poderemos fazer isso, assim

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

Por outro lado, window.addEventListener('hashchange',()=>{})seria acionado apenas se a parte após uma hashtag em uma URL mudar e window.addEventListener('popstate',()=>{})nem sempre funcionar.

Essa modificação, semelhante à resposta de Christian , modifica o objeto de história para adicionar algumas funcionalidades.

Por padrão, há um popstateevento, mas não há eventos para pushstate, e replacestate.

Isso modifica essas três funções para que todos disparem um locationchangeevento personalizado para você usar, e também pushstatee replacestateeventos se você quiser usá-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
fonte
2
Grande, o que eu precisava Graças
eslamb
Obrigado, realmente útil!
Thomas Lang
1
Existe uma maneira de fazer isso no IE? Como ele não suporta =>
joshuascotton 15/08/19
1
@joshuacotton => é uma função de seta, você pode substituir f => function fname () {...} por function (f) {return function fname () {...}}
aljgom
1
Isso é muito útil e funciona como um encanto. Essa deve ser a resposta aceita.
precisa saber é o seguinte
77

use este código

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

com jQuery

$(window).bind('hashchange', function() {
     //code
});
Behnam Mohammadi
fonte
7
Isso deve ser marcado como resposta. É uma linha, usa modelo de evento do navegador e não depende de recursos que consome tempo limite intermináveis
Nick Mitchell
1
Esta é na minha opinião a melhor resposta aqui. Nenhum plug-in e código mínimo
agDev
14
Não funciona em mudanças URL não de hash que parece ser muito populares, como a implementada por Slack
NycCompSci
26
Como essa é a melhor resposta se for acionada apenas quando houver um hash no URL?
Aaron
1
Você deve usar addEventListener em vez de substituir o valor onhashchange diretamente, caso algo mais queira ouvir também.
broken-e
55

EDITAR depois de um pouco de pesquisa:

De alguma forma, parece que fui enganado pela documentação presente nos documentos do Mozilla. O popstateevento (e sua função de retorno de chamada onpopstate) não é acionado sempre que o pushState()ou replaceState()é chamado no código. Portanto, a resposta original não se aplica a todos os casos.

No entanto, existe uma maneira de contornar isso fazendo o patch de macaco das funções de acordo com @ alpha123 :

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

Resposta original

Dado que o título desta pergunta é " Como detectar alterações na URL ", a resposta, quando você deseja saber quando o caminho completo é alterado (e não apenas a âncora de hash), é que você pode ouvir o popstateevento:

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

Referência para popstate no Mozilla Docs

Atualmente (janeiro de 2017), há suporte para o popstate em 92% dos navegadores em todo o mundo.

Cristian
fonte
11
Inacreditável que ainda temos que recorrer a tais hacks em 2018.
cabra
1
Isso funcionou para o meu caso de uso - mas apenas como @goat diz - é inacreditável que não há suporte nativo para isso ...
wasddd_
1
que argumentos? como eu configuraria o fireEvents?
SeanMC 30/01/19
2
Observe que isso também não é confiável em muitos casos. Por exemplo, ele não detectará a alteração do URL quando você clicar em diferentes variações de produtos da Amazon (os blocos abaixo do preço).
Thdan
2
Isto não detectar uma mudança do localhost / foo para localhost / baa se não estiver usando location.back ()
SuperUberDuper
53

Com jquery (e um plug-in), você pode fazer

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

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

Caso contrário, sim, você precisaria usar setInterval e verificar uma alteração no evento de hash (window.location.hash)

Atualizar! Um rascunho simples

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
fonte
posso detectar alterações de (window.location) e lidar com isso? (sem jquery)
BergP 13/09
6
Você pode @BergP, usando o ouvinte javascript comum:window.addEventListener("hashchange", hashChanged);
Ron
1
Esse curto intervalo de tempo é bom para o aplicativo? Ou seja, não mantém o navegador muito ocupado na execução da detect()função?
Hasib Mahmud
4
@HasibMahmud, esse código está fazendo uma verificação de igualdade a cada 100ms. Acabei de comparar no meu navegador que posso fazer 500 verificações de igualdade em menos de 1 ms. Portanto, esse código está usando 1/50000 da minha capacidade de processamento. Eu não me preocuparia muito.
Z5h 16/03/2015
24

Adicione um ouvinte de evento de alteração de hash!

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

Ou, para ouvir todas as alterações de URL:

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

Isso é melhor do que algo como o código abaixo, porque apenas uma coisa pode existir em window.onhashchange e você possivelmente substituirá o código de outra pessoa.

// Bad code example

window.onhashchange = function() { 
     // Code that overwrites whatever was previously in window.onhashchange  
}
Trev14
fonte
1
O estado pop só é acionado quando você
popa
8

esta solução funcionou para mim:

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
fonte
18
Este é um método bastante rudimentar, acho que podemos ter objetivos mais altos.
Carles Alcolea
8
Embora eu concorde com @CarlesAlcolea que isso pareça antigo, na minha experiência ainda é a única maneira de capturar 100% de todas as alterações de URL.
precisa saber é o seguinte
5
@ahofmann sugere (em uma edição que deveria ter sido um comentário) mudar setIntervalpara setTimeout: "usar setInterval () interromperá o navegador depois de um tempo, porque criará uma nova chamada para verificarURLchange () a cada segundo. setTimeout () é a solução correta, porque é chamada apenas uma vez ".
Divibisan
Essa foi a única solução para capturar todas as alterações de URL, mas o consumo de recursos está me forçando a procurar outras soluções.
ReturnTable
3
Ou, em vez de usar setTimeoutcomo sugere o @divibisan, mova a parte setIntervalexterna da função. checkURLchange();também se torna opcional.
Aristidesfl #
4

Embora seja uma pergunta antiga, o projeto da barra de localização é muito ú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
fonte
É para nodeJs, precisamos usar o browserify para usá-lo no lado do cliente. Não é?
hack4mer
1
Não, não é. Funciona no navegador
Ray Booysen
3

Para ouvir as alterações de URL, veja abaixo:

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

Use esse estilo se você pretende parar / remover o ouvinte após uma determinada condição.

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

Ao fazer uma pequena extensão do Chrome, enfrentei o mesmo problema com um problema adicional: às vezes, a página muda, mas não o URL.

Por exemplo, basta ir à página inicial do Facebook e clicar no botão 'Página inicial'. Você recarregará a página, mas o URL não será alterado (estilo de aplicativo de uma página).

Em 99% do tempo, estamos desenvolvendo sites para que possamos obter esses eventos de estruturas como Angular, React, Vue etc.

MAS , no meu caso de uma extensão do Chrome (no Vanilla JS), tive que ouvir um evento que será acionado para cada "alteração de página", que geralmente pode ser detectada pela alteração de URL, mas às vezes não.

Minha solução caseira foi a seguinte:

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

Então, basicamente, a solução leoneckert, aplicada ao histórico da janela, que muda quando uma página é alterada em um aplicativo de página única.

Não é ciência de foguetes, mas a solução mais limpa que encontrei, considerando que estamos apenas verificando uma igualdade de números aqui, e não objetos maiores ou todo o DOM.

Alburkerk
fonte
Sua solução é simples e funciona muito bem para extensões do Chrome. Gostaria de sugerir o uso do ID do vídeo do YouTube em vez da duração. stackoverflow.com/a/3452617/808901
Rod Lima
2
você não deve usar setInterval porque cada vez que você chama listen (xy), um novo intervalo é criado e você termina com milhares de intervalos.
Stef Chäser 4/03/19
1
Você está certo, notei que depois e não mudei meu post, vou editar isso. Na época, eu até encontrei uma falha do Google Chrome por causa de vazamentos de RAM. Obrigado pelo comentário
Alburkerk
window.historytem um comprimento máximo de 50 (pelo menos a partir do Chrome 80). Depois desse ponto, window.history.lengthsempre retorna 50. Quando isso acontecer, esse método falhará ao reconhecer quaisquer alterações.
Collin Krawll 16/03
1

Veja a função de descarregamento do jQuery. Ele lida com todas as coisas.

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

O evento de descarregamento é enviado ao elemento da janela quando o usuário sai da página. Isso pode significar uma de muitas coisas. O usuário pode ter clicado em um link para sair da página ou digitado um novo URL na barra de endereço. Os botões de avançar e voltar acionarão o evento. Fechar a janela do navegador fará com que o evento seja acionado. Mesmo um recarregamento de página criará primeiro um evento de descarregamento.

$(window).unload(
    function(event) {
        alert("navigating");
    }
);
Kostiantyn
fonte
2
Onde o conteúdo está no Ajax, o URL pode mudar sem que a janela seja descarregada. Esse script não detecta uma alteração de URL, embora ainda possa ser útil para alguns usuários que têm uma janela descarregada a cada alteração de URL.
Deborah
igual à pergunta @ranbuch, isso é específico apenas para páginas que não são aplicativos de página única e esse evento está assistindo apenas ao evento da janela de descarregamento, não à alteração da URL.
Ncubica
0
window.addEventListener("beforeunload", function (e) {
    // do something
}, false);
ranbuch
fonte
11
isso não vai funcionar no contexto de aplicações de página única desde o evento de descarregamento nunca será gatilho
ncubica
0

A resposta abaixo vem daqui (com sintaxe javascript antiga (nenhuma função de seta, suporte ao 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
fonte
0

Outra maneira simples de fazer isso é adicionar um evento de clique, por meio de um nome de classe, às tags de âncora na página para detectar quando ele foi clicado. Agora você pode usar o window.location.href para obter os dados da URL que você pode usar para executar sua solicitação ajax no servidor. Simples e Fácil.

Benjamin
fonte
-1

Você está iniciando um novo setIntervalem cada chamada, sem cancelar a anterior - provavelmente só queria ter umsetTimeout

Angelos Pikoulas
fonte