Як визначити, чи змінилася URL-адреса після хешу в JavaScript


160

Як я можу перевірити, чи змінилася URL-адреса в JavaScript? Наприклад, веб-сайти на зразок GitHub, які використовують AJAX, додадуть інформацію про сторінку після символу # для створення унікальної URL-адреси без перезавантаження сторінки. Який найкращий спосіб виявити, чи змінюється ця URL-адреса?

  • Є onload називається подія ще раз?
  • Чи є обробник події для URL-адреси?
  • Або необхідно перевіряти URL-адресу щосекунди, щоб виявити зміни?

1
Для відвідувачів в майбутньому, новий відповідь на @aljgom з 2018 року є найкращим рішенням: stackoverflow.com/a/52809105/151503
Redzarf

Відповіді:


106

У сучасних браузерах (IE8 +, FF3.6 +, Chrome) ви можете просто слухати hashchangeподію на window.

У деяких старих браузерах потрібен таймер, який постійно перевіряється location.hash. Якщо ви використовуєте jQuery, є плагін, який робить саме це.


132
Це, як я розумію, працює лише для зміни частини після знака # (звідси назва події)? І не для повної зміни URL-адреси, як, мабуть, мається на увазі заголовок питання.
NPC

13
@NPC Будь-який обробник для повного зміни URL-адреси (без тега прив’язки)?
Neha Choudhary

Вам рідко потрібні події тайм-ауту: використовуйте миші та клавішні пристрої для перевірки.
Сеїті

що робити, якщо зміниться шлях, а не хеш?
SuperUberDuper

@SuperUberDuper Якщо шлях зміниться через те, що користувач ініціював навігацію / натиснув посилання тощо, ви побачите лише beforeunloadподію. Якщо ваш код ініціював зміну URL-адреси, він найкраще знає.
фігаг

92

Мені хотілося додати locationchangeслухачів подій. Після модифікації нижче, ми зможемо зробити це, як це

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

На відміну від цього, window.addEventListener('hashchange',()=>{})запускається лише в тому випадку, якщо частина після хештегу в URL-адресі змінюється і window.addEventListener('popstate',()=>{})не завжди працює.

Ця модифікація, подібно до відповіді Крістіана , змінює об’єкт історії, щоб додати деяку функціональність.

За замовчуванням є popstateподія, але немає подій для pushstateта replacestate.

Це змінює ці три функції, щоб усі розпочали користувальницьку locationchangeподію для використання, а також pushstateі replacestateподії, якщо ви хочете використовувати ці:

/* 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'))
});


Дякую, дуже корисно!
Thomas Lang

1
Чи є спосіб це зробити в IE? Оскільки він не підтримує =>
joshuascotton

1
@joshuacotton => - це функція зі стрілкою, ви можете замінити f => функцію fname () {...} функцією (f) {функція повернення fname () {...}}
aljgom

1
Це дуже корисно і працює як шарм. Це має бути прийнятою відповіддю.
Кунал Парех

77

використовувати цей код

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

з jQuery

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

7
Це має бути позначено відповіддю. Це один рядок, використовує модель подій браузера і не покладається на нескінченні тайм-аути, що споживають ресурси
Нік Мітчелл

1
Це, на мій погляд, найкраща відповідь тут. Без плагіна та мінімального коду
agDev


26
Як це найкраща відповідь, якщо це спрацьовує лише тоді, коли в URL-адресі є хеш?
Аарон

1
Ви повинні використовувати addEventListener замість того, щоб замінювати значення onhashchange безпосередньо, якщо ви хочете слухати щось інше.
брейк-е

55

EDIT після невеликого дослідження:

Мені здається, що мене обдурила документація, представлена ​​на документах Mozilla. popstateПодія (і його функції зворотного виклику onpopstate) є не спрацьовує щоразу , коли pushState()або replaceState()викликаються в коді. Тому оригінальна відповідь застосовується не у всіх випадках.

Однак є спосіб обійти це, мавпочки виправляючи функції відповідно до @ alpha123 :

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

Оригінальна відповідь

Зважаючи на те, що заголовок цього питання - " Як виявити зміну URL-адреси ", відповідь, коли ви хочете знати, коли змінюється повний шлях (а не лише якірний хід), це те, що ви можете слухати popstateподію:

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

Довідка для попстату в документах Mozilla

Наразі (січень 2017 року) існує підтримка popstate від 92% браузерів у всьому світі.


11
Неймовірно, що ми все-таки повинні вдатися до таких хакків у 2018 році.
козел

1
Це спрацювало на моє використання - але так, як говорить @goat - неймовірно, що для цього немає нашої підтримки ...
wasddd_

1
які аргументи? як би я встановив fireEvents?
SeanMC

2
Зауважте, що це також є недостовірним у багатьох випадках. Наприклад, він не виявить зміни URL-адреси, якщо натиснути різні варіанти продуктів Amazon (плитки під ціною).
thoan

2
це не зможе виявити зміну з localhost / foo на localhost / baa, якщо не використовується location.back ()
SuperUberDuper

53

За допомогою jquery (і плагіна) ви можете зробити

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

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

Інакше так, вам доведеться використовувати setInterval і перевірити на зміну хеш-події (window.location.hash)

Оновлення! Простий проект

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

чи можу я виявити зміну (window.location) та обробити її? (без jquery)
BergP

6
Ви можете @BergP, використовуючи простий слухач javascript: window.addEventListener("hashchange", hashChanged);
Ron

1
Чи такий короткий інтервал часу корисний для програми? Тобто, чи не заважає він браузер занадто зайнятий у виконанні detect()функції?
Хасіб Махмуд

4
@HasibMahmud, цей код робить 1 перевірку рівності кожні 100 мс. Я просто встановив у своєму браузері, що я можу зробити 500 перевірок рівності за 1 мс. Отже, цей код використовує 1/50000-ту мою потужність обробки. Я б не надто хвилювався.
z5h

24

Додайте слухача подій зміни хешу!

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

Або прослухати всі зміни URL-адрес:

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

Це краще, ніж щось на зразок коду нижче, оскільки у window.onhashchange може існувати лише одне, і ви, можливо, будете перезаписати чужий код.

// Bad code example

window.onhashchange = function() { 
     // Code that overwrites whatever was previously in window.onhashchange  
}

1
Поп-стан спрацьовує лише тоді, коли ви з'являєте стан, а не натискаєте його
SeanMC

8

це рішення працювало для мене:

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

18
Це досить рудиментарний метод, я думаю, що ми можемо націлитися вище.
Карлес Алколія

8
Хоча я погоджуюся з @CarlesAlcolea, що це відчувається старим, але, на мій досвід, це все-таки єдиний спосіб зафіксувати 100% усіх змін у URL.
Трев14

5
@ahofmann пропонує (у редакції, яка мала б бути коментарем), змінивши setIntervalна setTimeout: "використання setInterval () зупинить браузер через деякий час, оскільки він створить новий виклик для перевіркиURLchange () щосекунди. setTimeout () є правильним рішенням, оскільки воно називається лише один раз ».
дивібізан

Це єдине рішення, яке дозволило знайти всі зміни URL-адрес, але споживання ресурсів змушує мене шукати інші рішення.
ReturnTable

3
Або замість того, щоб використовувати, setTimeoutяк @divibisan пропонує, перемістити setIntervalзовнішню частину функції. checkURLchange();також стає необов’язковим.
aristidesfl

4

Хоча давнє запитання, проект Location-bar дуже корисний.

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

Це для nodeJs, нам потрібно скористатися браузером, щоб використовувати його на стороні клієнта. Чи не ми?
hack4mer

1
Ні, це не так. Працює в браузері
Ray Booysen

3

Щоб прослухати зміни в URL-адресі, див. Нижче:

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

Використовуйте цей стиль, якщо ви хочете зупинити / видалити слухача після певних умов.

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

2

Роблячи невелике розширення з хромом, я зіткнувся з тією ж проблемою і з додатковою проблемою: Іноді сторінка змінюється, але не URL.

Наприклад, просто перейдіть на домашню сторінку Facebook і натисніть кнопку «Головна». Ви перезавантажите сторінку, але URL-адреса не зміниться (стиль однієї сторінки додатка).

У 99% часу ми розробляємо веб-сайти, щоб ми могли отримати такі події з Рамок, як Angular, React, Vue тощо.

Але в моєму випадку з розширенням для Chrome (у Vanilla JS) мені довелося прослухати подію, яка спричинить кожне "зміна сторінки", яке, як правило, може бути зафіксовано за зміненою URL-адресою, але іноді це не так.

Моє домашнє рішення було таке:

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

Таким чином, рішення leoneckert, застосоване до історії вікон, яке зміниться, коли сторінка зміниться в одному додатку сторінки.

Не ракетна наука, а найчистіше рішення, яке я знайшов, враховуючи, що ми перевіряємо лише цілу рівність тут, а не більші об'єкти чи цілий DOM.


Ви рішення просте і дуже добре працює для хромованих розширень. Я хотів би запропонувати використовувати ідентифікатор відео YouTube замість довжини. stackoverflow.com/a/3452617/808901
Rod Lima

2
не слід використовувати setInterval, оскільки кожен раз, коли ви викликаєте прослуховування (xy), створюється новий інтервал, і ви закінчуєте тисячі інтервалів.
Стеф Часер

1
Ви праві, я помітив, що після того, як я не змінив свою посаду, я це відредагую. Ще в той час я навіть стикався з збоєм Google Chrome через течі оперативної пам’яті. Дякую за коментар
Alburkerk

window.historyмає максимальну довжину 50 (принаймні, для Chrome 80). Після цього window.history.lengthзавжди повертається 50. Коли це станеться, цей метод не зможе визнати жодних змін.
Collin Krawll

1

Подивіться на функцію розвантаження jQuery. Він обробляє всі речі.

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

Подія завантаження надсилається елементу вікна, коли користувач переходить подалі від сторінки. Це може означати одну з багатьох речей. Користувач міг натиснути на посилання, щоб залишити сторінку, або ввести нову URL-адресу в адресному рядку. Кнопки вперед і назад спровокують подію. Закриття вікна браузера призведе до запуску події. Навіть перезавантаження сторінки спочатку створить подію завантаження.

$(window).unload(
    function(event) {
        alert("navigating");
    }
);

2
Якщо вміст розміщено в Ajaxed, URL-адреса може змінюватися без завантаження вікна. Цей скрипт не виявляє змін у URL-адресі, хоча він може бути корисним для деяких користувачів, у яких все-таки змінюється вікно під час кожної зміни URL-адреси.
Дебора

Як і питання @ranbuch, це характерно лише для сторінок, які не є додатком однієї сторінки, і ця подія переглядає лише події вікна завантаження, а не зміну URL-адреси.
ncubica

0
window.addEventListener("beforeunload", function (e) {
    // do something
}, false);

11
це не працюватиме в контексті додатків на одній сторінці, оскільки подія вивантаження ніколи не запуститься
ncubica

0

Відповідь нижче приходить звідси (зі старим синтаксисом javascript (без функції стрілки, підтримка 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"));
  });
})();

0

Ще один простий спосіб зробити це - додавши подію клацання через назву класу до якорних тегів на сторінці, щоб визначити, коли було натиснуто, то тепер ви можете використовувати window.location.href, щоб отримати дані URL, які ви можете використовувати для запуску запиту ajax на сервер. Просто і легко.


-1

Ви починаєте нове setIntervalпід час кожного дзвінка, не відміняючи попередній - напевно, ви мали намір лише матиsetTimeout

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.