Перехват вызова кнопки "Назад" в моем приложении AJAX: я не хочу, чтобы он что-то делал
У меня есть приложение AJAX. Пользователь нажимает кнопку, и отображение страницы изменяется. Они нажимают кнопку "назад", ожидая перехода в исходное состояние, но вместо этого переходят на предыдущую страницу в своем браузере.
Как я могу перехватить и переназначить событие "Назад"? Я изучал библиотеки типа RSH (которые я не мог заставить работать...), и я слышал, что использование хеш-тега как-то помогает, но я не могу понять это.
13 ответов
Ах, кнопка назад. Вы можете себе представить, что "back" запускает событие JavaScript, которое вы можете просто отменить следующим образом:
document.onHistoryGo = function() { return false; }
Нет так. Там просто нет такого события.
Если у вас действительно есть веб-приложение (в отличие от просто веб-сайта с некоторыми функциями ajaxy), разумно взять на себя кнопку возврата (с фрагментами в URL, как вы упомянули). Gmail делает это. Я говорю о том, когда ваше веб-приложение все на одной странице, все автономно.
Техника проста - всякий раз, когда пользователь предпринимает действия, которые изменяют вещи, перенаправляйте на тот же URL, на котором вы уже находитесь, но с другим фрагментом хеша. Например
window.location.hash = "#deleted_something";
...
window.location.hash = "#did_something_else";
Если общее состояние вашего веб-приложения можно изменить, это отличное место для использования хэша. Скажем, у вас есть список электронных писем, может быть, вы объедините все их идентификаторы и прочитанные / непрочитанные статусы и возьмете хэш MD5, используя его в качестве идентификатора фрагмента.
Этот вид перенаправления (только хэш) не пытается извлечь что-либо с сервера, но он вставляет слот в список истории браузера. Таким образом, в приведенном выше примере пользователь нажимает "назад", и теперь он показывает #deleted_something в адресной строке. Они наносят ответный удар снова, и они все еще на вашей странице, но без хеша. Затем вернитесь снова, и они действительно вернутся туда, откуда пришли.
Теперь самое сложное - заставить ваш JavaScript определять, когда пользователь наносит ответный удар (так что вы можете вернуть состояние). Все, что вы делаете, это смотрите расположение окна и смотрите, когда оно меняется. С опросом. (Я знаю, черт, опрос. Ну, нет ничего лучше кроссбраузерного прямо сейчас). Вы не сможете определить, продвинулись ли они вперед или назад, поэтому вам придется проявить творческий подход к своим хеш-идентификаторам. (Возможно, хэш объединен с порядковым номером...)
Это суть кода. (Это также, как работает плагин jQuery History.)
var hash = window.location.hash;
setInterval(function(){
if (window.location.hash != hash) {
hash = window.location.hash;
alert("User went back or forward to application state represented by " + hash);
}
}, 100);
Чтобы дать актуальный ответ на старый (но популярный) вопрос:
HTML5 представил
history.pushState()
а такжеhistory.replaceState()
методы, которые позволяют добавлять и изменять записи истории, соответственно. Эти методы работают в сочетании сwindow.onpopstate
событие.С помощью
history.pushState()
изменяет реферер, который используется в заголовке HTTP дляXMLHttpRequest
объекты, созданные после изменения состояния. Реферер будет URL-адрес документа, чье окноthis
во время созданияXMLHttpRequest
объект.
Источник: Управление историей браузера из Mozilla Developer Network.
Это очень легко отключить кнопку НАЗАД браузера, как код JQuery ниже:
// History API
if( window.history && window.history.pushState ){
history.pushState( "nohb", null, "" );
$(window).on( "popstate", function(event){
if( !event.originalEvent.state ){
history.pushState( "nohb", null, "" );
return;
}
});
}
Вы можете увидеть, как это работает, и больше примеров здесь dotnsf и здесь thecssninja
Спасибо!
Используя jQuery, я сделал простое решение:
$(window).bind('hashchange', function() {
top.location = '#main';
// Eventually alert the user describing what happened
});
Пока что проверено только в Google Chrome.
Это решило проблему для моего веб-приложения, которое также основано на AJAX.
Это, возможно, немного хакерски - но я бы назвал это элегантным взломом;-) Всякий раз, когда вы пытаетесь перейти назад, он добавляет хеш-часть в URI, которая технически то, что он затем пытается перейти назад.
Он перехватывает как нажатие кнопки браузера, так и кнопки мыши. И вы не можете перебить его задом наперед, щелкая несколько раз в секунду, что является проблемой, возникающей в решениях, основанных на setTimeout или setInterval.
Я действительно ценю объяснение, данное в ответе darkporter, но я думаю, что его можно улучшить, используя событиеhashchange. Как объяснил darkporter, вы хотите убедиться, что все ваши кнопки изменяют значение window.location.hash.
- Одним из способов является использование
<button>
элементы, а затем прикрепить к ним события, которые затем устанавливаютwindow.location.hash = "#!somehashstring";
, - Другой способ сделать это - просто использовать ссылки для ваших кнопок, например
<a href="#!somehashstring">Button 1</a>
, Хеш автоматически обновляется при нажатии на эти ссылки.
Причина, по которой после знака хеша у меня появляется восклицательный знак, заключается в том, что он соответствует парадигме Google "hashbang" ( подробнее об этом), что полезно, если вы хотите, чтобы поисковые системы проиндексировали его. Обычно вашей хеш-строкой являются пары имя / значение, например #!color=blue&shape=triangle
или список как #!/blue/triangle
- все, что имеет смысл для вашего веб-приложения.
Затем вам нужно только добавить этот бит кода, который будет срабатывать при каждом изменении значения хеша (в том числе при нажатии кнопки назад). Кажется, нет необходимости в опросе.
window.addEventListener("hashchange", function(){
console.log("Hash changed to", window.location.hash);
// .... Do your thing here...
});
Я не тестировал ничего кроме Chrome 36, но, согласно caniuse.com, это должно быть доступно в IE8+, FF21+, Chrome21+ и большинстве других браузеров, кроме Opera Mini.
Я разработал способ переопределить нормальное поведение истории и создать back
а также forward
события кнопки, используя API истории HTML5 (это не будет работать в IE 9). Это очень забавно, но эффективно, если вы хотите перехватывать события кнопок "назад" и "вперед" и обрабатывать их так, как вам хочется. Это может быть полезно в ряде сценариев, например, если вы отображаете окно удаленного рабочего стола и вам необходимо воспроизводить нажатия кнопок "назад" и "вперед" на удаленном компьютере.
Следующее переопределяет поведение кнопок "назад" и "вперед":
var myHistoryOverride = new HistoryButtonOverride(function()
{
console.log("Back Button Pressed");
return true;
},
function()
{
console.log("Forward Button Pressed");
return true;
});
Если вы вернули false в любой из этих функций обратного вызова, вы позволили бы браузеру продолжить обычную операцию возврата / пересылки и покинуть свою страницу.
Вот полный скрипт, необходимый:
function HistoryButtonOverride(BackButtonPressed, ForwardButtonPressed)
{
var Reset = function ()
{
if (history.state == null)
return;
if (history.state.customHistoryStage == 1)
history.forward();
else if (history.state.customHistoryStage == 3)
history.back();
}
var BuildURLWithHash = function ()
{
// The URLs of our 3 history states must have hash strings in them so that back and forward events never cause a page reload.
return location.origin + location.pathname + location.search + (location.hash && location.hash.length > 1 ? location.hash : "#");
}
if (history.state == null)
{
// This is the first page load. Inject new history states to help identify back/forward button presses.
var initialHistoryLength = history.length;
history.replaceState({ customHistoryStage: 1, initialHistoryLength: initialHistoryLength }, "", BuildURLWithHash());
history.pushState({ customHistoryStage: 2, initialHistoryLength: initialHistoryLength }, "", BuildURLWithHash());
history.pushState({ customHistoryStage: 3, initialHistoryLength: initialHistoryLength }, "", BuildURLWithHash());
history.back();
}
else if (history.state.customHistoryStage == 1)
history.forward();
else if (history.state.customHistoryStage == 3)
history.back();
$(window).bind("popstate", function ()
{
// Called when history navigation occurs.
if (history.state == null)
return;
if (history.state.customHistoryStage == 1)
{
if (typeof BackButtonPressed == "function" && BackButtonPressed())
{
Reset();
return;
}
if (history.state.initialHistoryLength > 1)
history.back(); // There is back-history to go to.
else
history.forward(); // No back-history to go to, so undo the back operation.
}
else if (history.state.customHistoryStage == 3)
{
if (typeof ForwardButtonPressed == "function" && ForwardButtonPressed())
{
Reset();
return;
}
if (history.length > history.state.initialHistoryLength + 2)
history.forward(); // There is forward-history to go to.
else
history.back(); // No forward-history to go to, so undo the forward operation.
}
});
};
Это работает по простой концепции. Когда наша страница загружается, мы создаем 3 различных состояния истории (с номерами 1, 2 и 3) и переходим в браузер к состоянию номер 2. Поскольку состояние 2 находится посередине, следующее событие навигации по истории переведет нас в состояние 1 или 3, и из этого мы можем определить, в каком направлении нажал пользователь. И вот так, мы перехватили событие кнопки назад или вперед. Затем мы обрабатываем его так, как хотим, и возвращаемся в состояние номер 2, чтобы мы могли зафиксировать следующее событие истории навигации.
Очевидно, вам нужно будет воздержаться от использования методов history.replaceState и history.pushState при использовании сценария HistoryButtonOverride, иначе вы бы его сломали.
Из-за соображений конфиденциальности невозможно отключить кнопку "Назад" или просмотреть историю пользователя, но можно создавать новые записи в этой истории, не меняя страницы. Всякий раз, когда ваше AJAX-приложение меняет состояние, обновляйте top.location
с новым фрагментом URI.
top.location = "#new-application-state";
Это создаст новую запись в стеке истории браузера. Ряд библиотек AJAX уже обрабатывают все скучные детали, такие как Really Simple History.
Я делаю что-то вроде этого. Я держу массив с предыдущими состояниями приложения.
Инициировано как:
var backstack = [];
Затем я слушаю изменения в хеше местоположения, и когда он меняется, я делаю это:
if (backstack.length > 1 && backstack[backstack.length - 2] == newHash) {
// Back button was probably pressed
backstack.pop();
} else
backstack.push(newHash);
Таким образом, у меня есть несколько простой способ отслеживать историю пользователей. Теперь, если я хочу внедрить кнопку "Назад" в приложение (а не в браузер-боттон), я просто заставлю это сделать:
window.location.hash = backstack.pop();
В моей ситуации я хотел запретить кнопке "Назад" отправить пользователя на последнюю страницу и вместо этого предпринять другое действие. С использованием hashchange
Событие, которое я придумал, сработало для меня. Этот скрипт предполагает, что страница, на которой вы его используете, еще не использует хэш, как в моем случае:
var hashChangedCount = -2;
$(window).on("hashchange", function () {
hashChangedCount++;
if (top.location.hash == "#main" && hashChangedCount > 0) {
console.log("Ah ah ah! You didn't say the magic word!");
}
top.location.hash = '#main';
});
top.location.hash = "#";
Проблема, с которой у меня возникли другие ответы, заключается в hashchange
событие не сработает. В зависимости от вашей ситуации, это может работать и для вас.
Кнопка назад в браузерах обычно пытается вернуться к предыдущему адресу. в javascript браузера отсутствует собственное событие нажатия кнопки "Назад", но вы можете взломать его, поиграв с местоположением и хэшем, чтобы изменить состояние вашего приложения.
представьте простое приложение с двумя видами:
- вид по умолчанию с кнопкой извлечения
- просмотр результатов
чтобы реализовать это, следуйте этому примеру кода:
function goToView (viewName) {
// before you want to change the view, you have to change window.location.hash.
// it allows you to go back to the previous view with the back button.
// so use this function to change your view instead of directly do the job.
// page parameter is your key to understand what view must be load after this.
window.location.hash = page
}
function loadView (viewName) {
// change dom here based on viewName, for example:
switch (viewName) {
case 'index':
document.getElementById('result').style.display = 'none'
document.getElementById('index').style.display = 'block'
break
case 'result':
document.getElementById('index').style.display = 'none'
document.getElementById('result').style.display = 'block'
break
default:
document.write('404')
}
}
window.addEventListener('hashchange', (event) => {
// oh, the hash is changed! it means we should do our job.
const viewName = window.location.hash.replace('#', '') || 'index'
// load that view
loadView(viewName)
})
// load requested view at start.
if (!window.location.hash) {
// go to default view if there is no request.
goToView('index')
} else {
// load requested view.
loadView(window.location.hash.replace('#', ''))
}
Просто два разных подхода.
Performance.getEntriesByType вернет «reload», «navigation», «back_forward» или «prerender», тогда вы можете создать, если еще с ним;
performance.getEntriesByType("navigation").forEach((entry) => {
console.log(entry.type)
});
Печенье;
var HisLen = getCookie("HisLen");
if (HisLen == history.length) {
console.log("Back or forward or reload button clicked");
}
setTempCookies("HisLen", history.length, 1000 * 3600);
Если вы используете React, вы можете использовать модуль NPM: use-history-back-trap. Это специальный хук React, который перехватывает обратную навигацию и позволяет возобновить ее позже, если это необходимо. Или выполните другое действие в «approveNavigation», если это необходимо (например, перейдите в другое место).
Использование очень простое:
const SomeFunctionalReactComponent = () => {
useHistoryBackTrap(approveNavigation)
// ...
}
где Appnavigation — это ваша функция, которая возвращает true (или Promise), когда вы хотите продолжить обратную навигацию.
У @gitaarik есть ответ , но, к сожалению, он не приводит примера.
Вот минимальный рабочий пример, иллюстрирующий, как можно использовать API истории , чтобы предотвратить возврат на предыдущую страницу, когда пользователь отвечает:
The state
Объект фактически не используется в этом примере. Помимо прочего, его можно использовать для восстановления состояния страницы после перехода, как показано здесь .
Также ознакомьтесь с документацией Mozilla, объясняющей, когда запускается событие popstate .