Jak wykryć, czy adres URL zmienił się po krzyżyku w JavaScript

160

Jak mogę sprawdzić, czy adres URL zmienił się w JavaScript? Na przykład strony internetowe takie jak GitHub, które używają AJAX, dołączą informacje o stronie po symbolu #, aby utworzyć unikalny adres URL bez ponownego ładowania strony. Jaki jest najlepszy sposób na wykrycie, czy ten adres URL się zmienia?

  • Czy onloadwydarzenie zostało ponownie wywołane?
  • Czy istnieje program obsługi zdarzeń dla adresu URL?
  • A może adres URL musi być sprawdzany co sekundę, aby wykryć zmianę?
AJ00200
źródło
1
Dla przyszłych odwiedzających najlepszą odpowiedzią jest @aljgom z 2018 roku: stackoverflow.com/a/52809105/151503
Redzarf

Odpowiedzi:

106

W nowoczesnych przeglądarkach (IE8 +, FF3.6 +, Chrome) można po prostu posłuchać hashchangewydarzenia na window.

W niektórych starych przeglądarkach potrzebny jest licznik czasu, który nieustannie sprawdza location.hash. Jeśli używasz jQuery, istnieje wtyczka , która dokładnie to robi.

phihag
źródło
132
To, jak rozumiem, działa tylko przy zmianie części po znaku # (stąd nazwa zdarzenia)? I nie dla pełnej zmiany adresu URL, jak wydaje się sugerować tytuł pytania.
NPC
13
@NPC Jakiś program obsługi do pełnej zmiany adresu URL (bez tagu kotwicy)?
Neha Choudhary,
Rzadko potrzebujesz zdarzeń limitu czasu: użyj zdarzeń myszy i klawiatury do sprawdzenia.
Sjeiti
co się stanie, jeśli ścieżka się zmieni, a nie hash?
SuperUberDuper
@SuperUberDuper Jeśli ścieżka zmieni się, ponieważ użytkownik zainicjował nawigację / kliknął łącze itp., Zobaczysz tylko beforeunloadzdarzenie. Jeśli Twój kod zainicjował zmianę adresu URL, wie najlepiej.
phihag
92

Chciałem mieć możliwość dodawania locationchangesłuchaczy zdarzeń. Po poniższej modyfikacji będziemy mogli to zrobić w ten sposób

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

W przeciwieństwie do tego, window.addEventListener('hashchange',()=>{})będzie uruchamiane tylko wtedy, gdy zmieni się część po hashtagu w adresie URL i window.addEventListener('popstate',()=>{})nie zawsze działa.

Ta modyfikacja, podobnie jak odpowiedź Christiana , modyfikuje obiekt historii, aby dodać pewną funkcjonalność.

Domyślnie istnieje popstatewydarzenie, ale nie ma wydarzeń dla pushstatei replacestate.

Modyfikuje te trzy funkcje, dzięki czemu cały ogień zwyczaj locationchangewydarzenie do użycia, a także pushstatei replacestatewydarzenia, jeśli chcesz korzystać z tych:

/* 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
źródło
2
Świetnie, czego potrzebowałem Dzięki
eslamb
Dzięki, bardzo pomocne!
Thomas Lang
1
Czy jest na to sposób w IE? Ponieważ nie obsługuje =>
joshuascotton
1
@joshuacotton => to funkcja strzałkowa, możesz zastąpić f => function fname () {...} funkcją function (f) {return function fname () {...}}
aljgom
1
Jest to bardzo pomocne i działa jak urok. To powinna być akceptowana odpowiedź.
Kunal Parekh
77

użyj tego kodu

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

z jQuery

$(window).bind('hashchange', function() {
     //code
});
Behnam Mohammadi
źródło
7
To musi być oznaczone jako odpowiedź. To jedna linia, używa modelu zdarzeń przeglądarki i nie polega na niekończących się limitach czasu pochłaniających zasoby
Nick Mitchell,
1
To moim zdaniem najlepsza odpowiedź tutaj. Brak wtyczki i minimalny kod
agDev
14
Nie działa w przypadku zmian
adresów
26
Jak to jest najlepsza odpowiedź, jeśli jest uruchamiana tylko wtedy, gdy w adresie URL znajduje się skrót?
Aaron,
1
Powinieneś używać addEventListener zamiast bezpośrednio zastępować wartość onhashchange, na wypadek, gdyby coś innego również chciało słuchać.
broken-e
55

EDYTUJ po krótkiej analizie:

Wygląda na to, że dałem się nabrać na dokumentację obecną w dokumentach Mozilli. popstateWydarzenie (i jego funkcja zwrotna onpopstate) są nie wywołał , gdy pushState()albo replaceState()są wywoływane w kodzie. Dlatego oryginalna odpowiedź nie ma zastosowania we wszystkich przypadkach.

Istnieje jednak sposób na obejście tego przez małpowanie funkcji zgodnie z @ alpha123 :

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

Oryginalna odpowiedź

Biorąc pod uwagę, że tytuł tego pytania brzmi „ Jak wykryć zmianę adresu URL ”, jeśli chcesz wiedzieć, kiedy zmienia się pełna ścieżka (a nie tylko zakotwiczenie skrótu), możesz nasłuchiwać popstatezdarzenia:

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

Odniesienie do popstate w Mozilla Docs

Obecnie (styczeń 2017) popstate obsługuje 92% przeglądarek na całym świecie.

Cristian
źródło
11
Niewiarygodne, że nadal musimy uciekać się do takich hacków w 2018 r.
koza
1
To zadziałało w moim przypadku użycia - ale tak jak mówi @goat - to niewiarygodne, że nie ma natywnego wsparcia dla tego ...
wasddd_
1
jakie argumenty? jak ustawić fireEvents?
SeanMC
2
Należy pamiętać, że w wielu przypadkach jest to również zawodne. Na przykład nie wykryje zmiany adresu URL po kliknięciu różnych odmian produktów Amazon (kafelki pod ceną).
1919
2
to nie wykryje zmiany z localhost / foo na localhost / baa, jeśli nie używasz location.back ()
SuperUberDuper
53

Dzięki jquery (i wtyczce) możesz to zrobić

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

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

W przeciwnym razie tak, musiałbyś użyć setInterval i sprawdzić, czy wystąpiła zmiana w zdarzeniu hash (window.location.hash)

Aktualizacja! Prosty szkic

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
źródło
czy mogę wykryć zmianę (window.location) i sobie z tym poradzić? (bez jQuery)
BergP
6
Możesz @BergP, używając zwykłego nasłuchiwania javascript: window.addEventListener("hashchange", hashChanged);
Ron,
1
Czy taki krótki odstęp czasu jest dobry dla aplikacji? To znaczy, czy nie powoduje to, że przeglądarka jest zbyt zajęta wykonywaniem detect()funkcji?
Hasib Mahmud
4
@HasibMahmud, ten kod wykonuje 1 kontrolę równości co 100 ms. Właśnie przetestowałem w mojej przeglądarce, że mogę przeprowadzić 500 testów równości w czasie poniżej 1 ms. Więc ten kod wykorzystuje 1/50000 mojej mocy obliczeniowej. Nie martwiłbym się zbytnio.
z5h
24

Dodaj odbiornik zdarzeń zmiany skrótu!

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

Lub, aby odsłuchać wszystkie zmiany adresu URL:

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

Jest to lepsze niż coś takiego jak poniższy kod, ponieważ tylko jedna rzecz może istnieć w window.onhashchange i prawdopodobnie nadpisujesz kod kogoś innego.

// Bad code example

window.onhashchange = function() { 
     // Code that overwrites whatever was previously in window.onhashchange  
}
Trev14
źródło
1
stan pop wyzwala się tylko wtedy, gdy wyskakujesz stan, a nie push go
SeanMC
8

to rozwiązanie zadziałało dla mnie:

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
źródło
18
To raczej podstawowa metoda, myślę, że możemy mierzyć wyżej.
Carles Alcolea
8
Chociaż zgadzam się z @CarlesAlcolea, że ​​to wydaje się stare, z mojego doświadczenia wynika, że ​​jest to jedyny sposób na wyłapanie 100% wszystkich zmian adresu URL.
Trev14
5
@ahofmann sugeruje (w edycji, która powinna być komentarzem) zmiana setIntervalna setTimeout: "użycie setInterval () spowoduje zatrzymanie przeglądarki po chwili, ponieważ co sekundę będzie tworzyć nowe wywołanie funkcji checkURLchange (). setTimeout () jest właściwym rozwiązaniem, ponieważ jest wywoływane tylko raz. "
divibisan
To było jedyne rozwiązanie, które pozwoliło wyłapać wszystkie zmiany adresu URL, ale zasoby pochłaniające zmuszają mnie do szukania innych rozwiązań.
ReturnTable
3
Lub zamiast używać tego, setTimeoutco sugeruje @divibisan, przesuń setIntervalzewnętrzną stronę funkcji. checkURLchange();również staje się opcjonalne.
aristidesfl
4

Chociaż to stare pytanie, projekt paska lokalizacji jest bardzo przydatny.

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
źródło
To jest dla nodeJs, musimy użyć browserify, aby używać go po stronie klienta. Nie my?
hack4mer
1
Nie, nie jest. Działa w przeglądarce
Ray Booysen
3

Aby odsłuchać zmiany adresu URL, zobacz poniżej:

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

Użyj tego stylu, jeśli zamierzasz zatrzymać / usunąć słuchacza po spełnieniu pewnych warunków.

window.addEventListener('popstate', function(e) {
   console.log('url changed')
});
codejockie
źródło
2

Robiąc małe rozszerzenie do Chrome, napotkałem ten sam problem z dodatkowym problemem: czasami strona się zmienia, ale nie adres URL.

Na przykład po prostu przejdź do strony głównej Facebooka i kliknij przycisk „Strona główna”. Ponownie załadujesz stronę, ale adres URL nie ulegnie zmianie (styl aplikacji jednostronicowej).

W 99% tworzymy strony internetowe, abyśmy mogli pobierać te zdarzenia z ram, takich jak Angular, React, Vue itp.

ALE , w moim przypadku rozszerzenia Chrome (w Vanilla JS), musiałem słuchać zdarzenia, które będzie wyzwalane przy każdej „zmianie strony”, które generalnie można wychwycić po zmianie adresu URL, ale czasami tak się nie dzieje.

Moje domowe rozwiązanie było następujące:

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

A więc w zasadzie rozwiązanie leoneckert, zastosowane do historii okien, które zmieni się, gdy strona zmieni się w aplikacji pojedynczej strony.

To nie fizyka jądrowa, ale najczystsze rozwiązanie, jakie znalazłem, biorąc pod uwagę, że sprawdzamy tutaj tylko całkowitą równość, a nie większe obiekty lub cały DOM.

Alburkerk
źródło
Twoje rozwiązanie jest proste i działa bardzo dobrze w przypadku rozszerzeń Chrome. Chciałbym zasugerować użycie identyfikatora wideo YouTube zamiast długości. stackoverflow.com/a/3452617/808901
Rod Lima
2
nie powinieneś używać setInterval, ponieważ za każdym razem, gdy dzwonisz do nasłuchuj (xy), tworzony jest nowy interwał, który kończy się tysiącami interwałów.
Stef Chäser
1
Masz rację, zauważyłem, że po i nie zmieniłem mojego posta, zmienię to. W tamtym czasie napotkałem nawet awarię przeglądarki Google Chrome z powodu wycieków pamięci RAM. Dziękuję za komentarz
Alburkerk
window.historyma maksymalną długość 50 (przynajmniej od Chrome 80). Po tym momencie window.history.lengthzawsze zwraca 50. W takim przypadku ta metoda nie rozpozna żadnych zmian.
Collin Krawll
1

Spójrz na funkcję wyładowania jQuery. Obsługuje wszystkie rzeczy.

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

Zdarzenie unload jest wysyłane do elementu window, gdy użytkownik opuści stronę. Może to oznaczać jedną z wielu rzeczy. Użytkownik mógł kliknąć łącze, aby opuścić stronę lub wpisać nowy adres URL w pasku adresu. Przyciski do przodu i do tyłu wywołają zdarzenie. Zamknięcie okna przeglądarki spowoduje wywołanie zdarzenia. Nawet ponowne załadowanie strony spowoduje najpierw utworzenie zdarzenia rozładowania.

$(window).unload(
    function(event) {
        alert("navigating");
    }
);
Kostiantyn
źródło
2
Tam, gdzie zawartość jest Ajaxed, adres URL może się zmienić bez wyładowywania okna. Ten skrypt nie wykrywa zmiany adresu URL, chociaż może być pomocny dla niektórych użytkowników, którzy mają zwalnianie okna przy każdej zmianie adresu URL.
Deborah
tak samo jak @ranbuch pytanie, jest to specyficzne tylko dla stron, które nie są aplikacjami jednostronicowymi, a to zdarzenie obserwuje tylko zdarzenie okna rozładowania, a nie zmianę adresu URL.
ncubica
0
window.addEventListener("beforeunload", function (e) {
    // do something
}, false);
ranbuch
źródło
11
to nie zadziała w kontekście aplikacji jednostronicowych, ponieważ zdarzenie wyładowania nigdy nie zostanie
wyzwolone
0

Odpowiedź poniżej pochodzi stąd (ze starą składnią javascript (bez funkcji strzałek, obsługa 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
źródło
0

Innym prostym sposobem na to jest dodanie zdarzenia kliknięcia, poprzez nazwę klasy do znaczników kotwicy na stronie, aby wykryć, kiedy została kliknięta, a następnie możesz teraz użyć window.location.href do pobrania danych adresu URL, które możesz użyć do uruchomienia żądania AJAX do serwera. Proste i łatwe.

Benzoes
źródło
-1

Zaczynasz nowe setIntervalprzy każdym połączeniu, bez anulowania poprzedniego - prawdopodobnie chciałeś mieć tylkosetTimeout

Angelos Pikoulas
źródło