Cara mendeteksi apakah URL telah berubah setelah hash dalam JavaScript

160

Bagaimana saya dapat memeriksa apakah suatu URL telah berubah dalam JavaScript? Misalnya, situs web seperti GitHub, yang menggunakan AJAX, akan menambahkan informasi halaman setelah simbol # untuk membuat URL unik tanpa memuat ulang halaman. Apa cara terbaik untuk mendeteksi jika URL ini berubah?

  • Apakah onloadacara dipanggil lagi?
  • Apakah ada pengendali acara untuk URL?
  • Atau haruskah URL diperiksa setiap detik untuk mendeteksi perubahan?
AJ00200
sumber
1
Untuk pengunjung masa depan, jawaban baru oleh @aljgom dari 2018 adalah solusi terbaik: stackoverflow.com/a/52809105/151503
Redzarf

Jawaban:

106

Di browser modern (IE8 +, FF3.6 +, Chrome), Anda dapat mendengarkan hashchangeacara tersebut window.

Di beberapa browser lama, Anda perlu timer yang terus-menerus memeriksa location.hash. Jika Anda menggunakan jQuery, ada plugin yang melakukan hal itu.

phihag
sumber
132
Ini, seperti yang saya mengerti, hanya berfungsi untuk perubahan bagian setelah tanda # (karenanya nama acara)? Dan tidak untuk perubahan URL lengkap, seperti yang tersirat oleh judul pertanyaan.
NPC
13
@NPC Any handler untuk perubahan URL lengkap (tanpa jangkar tag)?
Neha Choudhary
Anda jarang membutuhkan acara timeout: gunakan mouse dan keyboard untuk memeriksa.
Sjeiti
bagaimana jika pathnya berubah, bukan hash?
SuperUberDuper
@SuperUberDuper Jika jalur berubah karena pengguna memulai navigasi / mengklik tautan, dll., Maka Anda hanya akan melihat suatu beforeunloadperistiwa. Jika kode Anda memulai perubahan URL, ia tahu yang terbaik.
phihag
92

Saya ingin dapat menambahkan locationchangependengar acara. Setelah modifikasi di bawah, kita akan dapat melakukannya, seperti ini

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

Sebaliknya, window.addEventListener('hashchange',()=>{})hanya akan menyala jika bagian setelah tagar di url berubah, dan window.addEventListener('popstate',()=>{})tidak selalu berfungsi.

Modifikasi ini, mirip dengan jawaban Christian , memodifikasi objek sejarah untuk menambahkan beberapa fungsi.

Secara default, ada popstateacara, tetapi tidak ada acara untuk pushstate, dan replacestate.

Ini memodifikasi ketiga fungsi ini sehingga semua menjalankan locationchangeacara khusus untuk Anda gunakan, dan juga pushstatedan replacestateacara jika Anda ingin menggunakannya:

/* 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
sumber
2
Hebat, apa yang saya butuhkan Terima kasih
eslamb
Terima kasih, sangat membantu!
Thomas Lang
1
Apakah ada cara untuk melakukan ini di IE? Karena tidak mendukung =>
joshuascotton
1
@joshuacotton => adalah fungsi panah, Anda dapat mengganti f => function fname () {...} dengan function (f) {return function fname () {...}}
aljgom
1
Ini sangat membantu dan bekerja seperti pesona. Ini harus menjadi jawaban yang diterima.
Kunal Parekh
77

gunakan kode ini

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

dengan jQuery

$(window).bind('hashchange', function() {
     //code
});
Behnam Mohammadi
sumber
7
Ini harus ditandai jawabannya. Ini satu baris, menggunakan model acara browser dan tidak bergantung pada habisnya waktu habis sumber daya
Nick Mitchell
1
Ini menurut saya jawaban terbaik di sini. Tidak ada plugin dan kode minimal
agDev
14
Tidak bekerja pada perubahan url non-hash yang tampaknya sangat populer seperti yang diterapkan oleh Slack
NycCompSci
26
Bagaimana ini jawaban terbaik jika ini hanya dipicu ketika ada hash di url?
Aaron
1
Anda harus menggunakan addEventListener alih-alih mengganti nilai onhashchange secara langsung, jika ada hal lain yang ingin didengarkan juga.
broken-e
55

EDIT setelah sedikit meneliti:

Sepertinya saya tertipu oleh dokumentasi yang ada di Mozilla docs. The popstateevent (dan fungsi callback nya onpopstate) yang tidak dipicu setiap kali pushState()atau replaceState()disebut dalam kode. Karenanya jawaban asli tidak berlaku dalam semua kasus.

Namun ada cara untuk menghindari ini dengan menambal fungsi-fungsi menurut @ alpha123 :

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

Jawaban asli

Mengingat bahwa judul pertanyaan ini adalah " Bagaimana mendeteksi perubahan URL " jawabannya, ketika Anda ingin tahu kapan path lengkap berubah (dan bukan hanya anchor hash), adalah Anda dapat mendengarkan popstateacara:

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

Referensi untuk popstate di Mozilla Docs

Saat ini (Jan 2017) ada dukungan untuk popstate dari 92% browser di seluruh dunia.

Cristian
sumber
11
Sulit dipercaya bahwa kita masih harus menggunakan peretasan seperti itu pada tahun 2018.
kambing
1
Ini berfungsi untuk kasus penggunaan saya - tetapi seperti yang dikatakan @goat - tidak dapat dipercaya bahwa tidak ada dukungan asli untuk ini ...
wasddd_
1
argumen apa? bagaimana saya mengatur FireEvents?
SeanMC
2
Perhatikan bahwa ini juga tidak dapat diandalkan dalam banyak kasus. Misalnya, itu tidak akan mendeteksi perubahan URL ketika Anda mengklik variasi produk Amazon yang berbeda (ubin di bawah harga).
thdoan
2
ini tidak akan mendeteksi perubahan dari localhost / foo ke localhost / baa jika tidak menggunakan location.back ()
SuperUberDuper
53

Dengan jquery (dan plug-in) yang bisa Anda lakukan

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

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

Kalau tidak ya, Anda harus menggunakan setInterval dan memeriksa perubahan dalam acara hash (window.location.hash)

Memperbarui! Draf sederhana

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
sumber
dapatkah saya mendeteksi perubahan (window.location) dan menanganinya? (tanpa jquery)
BergP
6
Anda dapat @BergP, Menggunakan pendengar javascript biasa: window.addEventListener("hashchange", hashChanged);
Ron
1
Apakah interval waktu singkat seperti itu baik untuk aplikasi? Artinya, bukankah itu membuat browser terlalu sibuk dalam menjalankan detect()fungsi?
Hasib Mahmud
4
@ HasibMahmud, kode itu melakukan 1 pemeriksaan kesetaraan setiap 100ms. Saya baru saja membandingkan di browser saya bahwa saya dapat melakukan 500 pemeriksaan kesetaraan di bawah 1ms. Jadi kode itu menggunakan 1/50000 daya pemrosesan saya. Saya tidak akan terlalu khawatir.
z5h
24

Tambahkan hash ubah pendengar acara!

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

Atau, untuk mendengarkan semua perubahan URL:

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

Ini lebih baik daripada sesuatu seperti kode di bawah ini karena hanya satu hal yang bisa ada di window.onhashchange dan Anda mungkin akan menimpa kode orang lain.

// Bad code example

window.onhashchange = function() { 
     // Code that overwrites whatever was previously in window.onhashchange  
}
Trev14
sumber
1
negara pop hanya terpicu ketika Anda membuat negara bagian, bukan mendorong negara
SeanMC
8

solusi ini bekerja untuk saya:

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
sumber
18
Ini adalah metode yang agak belum sempurna, saya pikir kita bisa bertujuan lebih tinggi.
Carles Alcolea
8
Meskipun saya setuju dengan @CarlesAlcolea bahwa ini terasa tua, menurut pengalaman saya, ini masih satu-satunya cara untuk menangkap 100% dari semua perubahan url.
Trev14
5
@ahofmann menyarankan (dalam edit yang seharusnya menjadi komentar) berubah setIntervalmenjadi setTimeout: "menggunakan setInterval () akan membuat Browser berhenti setelah beberapa saat, karena itu akan membuat panggilan baru untuk memeriksaURLchange () setiap detik. setTimeout () adalah solisi yang benar, karena itu disebut hanya sekali. "
divibisan
Ini telah menjadi satu-satunya solusi untuk menangkap semua perubahan URL tetapi sumber daya memaksa saya untuk mencari beberapa solusi lain.
ReturnTable
3
Atau alih-alih menggunakan setTimeoutseperti @divibisan menyarankan, pindahkan bagian setIntervalluar fungsi. checkURLchange();juga menjadi opsional.
aristidesfl
4

Meskipun merupakan pertanyaan lama, proyek Location-bar sangat berguna.

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
sumber
Ini untuk nodeJs, kita perlu menggunakan browserify untuk menggunakannya di sisi klien. Bukan?
hack4mer
1
Bukan itu. Berfungsi di browser
Ray Booysen
3

Untuk mendengarkan perubahan url, lihat di bawah:

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

Gunakan gaya ini jika Anda bermaksud untuk menghentikan / menghapus pendengar setelah beberapa kondisi tertentu.

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

Saat melakukan sedikit ekstensi chrome, saya menghadapi masalah yang sama dengan masalah tambahan: Kadang-kadang, halaman berubah tetapi tidak URL.

Misalnya, cukup buka Beranda Facebook, dan klik tombol 'Beranda'. Anda akan memuat ulang halaman tetapi URL tidak akan berubah (gaya aplikasi satu halaman).

99% dari waktu, kami mengembangkan situs web sehingga kami bisa mendapatkan acara tersebut dari Kerangka seperti Angular, React, Vue dll.

TAPI , dalam kasus ekstensi Chrome saya (di Vanilla JS), saya harus mendengarkan acara yang akan memicu untuk setiap "perubahan halaman", yang umumnya dapat ditangkap oleh URL yang diubah, tetapi kadang-kadang tidak.

Solusi buatan saya adalah sebagai berikut:

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

Jadi pada dasarnya solusi leoneckert, diterapkan pada window history, yang akan berubah ketika suatu halaman berubah dalam satu aplikasi halaman.

Bukan ilmu roket, tetapi solusi terbersih yang saya temukan, mengingat kita hanya memeriksa kesetaraan bilangan bulat di sini, dan bukan objek yang lebih besar atau seluruh DOM.

Alburkerk
sumber
Solusi Anda sederhana dan bekerja sangat baik untuk ekstensi chrome. Saya ingin menyarankan untuk menggunakan id video YouTube alih-alih panjangnya. stackoverflow.com/a/3452617/808901
Rod Lima
2
Anda tidak boleh menggunakan setInterval karena setiap kali Anda menelepon dengarkan (xy), Interval baru dibuat dan Anda berakhir dengan ribuan interval.
Stef Chäser
1
Anda benar, saya perhatikan bahwa setelah dan tidak mengubah posting saya, saya akan mengeditnya. Dahulu kala saya bahkan mengalami crash Google Chrome karena kebocoran RAM. Terima kasih atas komentarnya
Alburkerk
window.historymemiliki panjang maksimal 50 (setidaknya pada Chrome 80). Setelah itu, window.history.lengthselalu kembali 50. Ketika itu terjadi, metode ini akan gagal mengenali perubahan apa pun.
Collin Krawll
1

Lihatlah fungsi jQuery unload. Ini menangani semua hal.

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

Acara bongkar dikirim ke elemen jendela ketika pengguna menavigasi jauh dari halaman. Ini bisa berarti satu dari banyak hal. Pengguna bisa mengklik tautan untuk meninggalkan halaman, atau mengetik URL baru di bilah alamat. Tombol maju dan mundur akan memicu acara. Menutup jendela browser akan menyebabkan acara menjadi terpicu. Bahkan memuat ulang halaman terlebih dahulu akan membuat acara bongkar.

$(window).unload(
    function(event) {
        alert("navigating");
    }
);
Kostiantyn
sumber
2
Di mana konten di-Ajax, url dapat berubah tanpa jendela diturunkan. Skrip ini tidak mendeteksi perubahan url, meskipun mungkin masih bermanfaat bagi beberapa pengguna yang memiliki jendela bongkar pada setiap perubahan url.
Deborah
sama dengan pertanyaan @ranbuch, ini hanya khusus untuk halaman yang bukan aplikasi satu halaman, dan acara ini hanya menonton acara bongkar jendela, bukan perubahan url.
ncubica
0
window.addEventListener("beforeunload", function (e) {
    // do something
}, false);
ranbuch
sumber
11
ini tidak akan berfungsi dalam konteks aplikasi satu halaman karena peristiwa unload tidak akan pernah memicu
ncubica
0

Jawaban di bawah ini berasal dari sini (dengan sintaks javascript lama (tidak ada fungsi panah, mendukung 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
sumber
0

Cara sederhana lain yang dapat Anda lakukan adalah dengan menambahkan acara klik, melalui nama kelas ke jangkar tag pada halaman untuk mendeteksi kapan telah diklik, maka Anda sekarang dapat menggunakan window.location.href untuk mendapatkan data url yang Anda dapat menggunakan untuk menjalankan permintaan ajax Anda ke server. Sederhana dan Mudah.

Benjamin
sumber
-1

Anda memulai yang baru setIntervaldi setiap panggilan, tanpa membatalkan yang sebelumnya - mungkin Anda hanya bermaksud memilikisetTimeout

Angelos Pikoulas
sumber