Перехват вызова на кнопку возврата в моем приложении AJAX - PullRequest
72 голосов
/ 04 декабря 2009

У меня есть приложение AJAX. Пользователь нажимает кнопку, и отображение страницы изменяется. Они нажимают кнопку «назад», ожидая перехода в исходное состояние, но вместо этого переходят на предыдущую страницу в своем браузере.

Как я могу перехватить и переназначить событие кнопки возврата? Я посмотрел библиотеки типа RSH (которые я не смог заставить работать ...), и я слышал, что использование хеш-тега как-то помогает, но я не могу понять это.

Ответы [ 10 ]

114 голосов
/ 04 декабря 2009

Ах, кнопка назад. Вы можете себе представить, что «back» запускает событие JavaScript, которое вы можете просто отменить, например:

document.onHistoryGo = function() { return false; }

Нет, не так. Просто нет такого события.

Если у вас действительно есть веб-приложение (в отличие от просто веб-сайта с некоторыми функциями ajaxy), разумно перейти на кнопку возврата (с фрагментами в URL, как вы упомянули). Gmail делает это. Я говорю о том, когда ваше веб-приложение все на одной странице, все автономно.

Техника проста & mdash; всякий раз, когда пользователь предпринимает действия, которые изменяют вещи, перенаправляйте на тот же URL, который вы уже используете, но с другим фрагментом хеша. Э.Г.

window.location.hash = "#deleted_something";
...
window.location.hash = "#did_something_else";

Если общее состояние вашего веб-приложения можно изменить, это отличное место для использования хэша. Скажем, у вас есть список электронных писем, может быть, вы объедините все их идентификаторы и прочитанные / непрочитанные состояния и возьмете хеш MD5, используя его в качестве идентификатора фрагмента.

Этот тип перенаправления (только для хэша) не пытается извлечь что-либо с сервера, но он вставляет слот в список истории браузера. Таким образом, в приведенном выше примере пользователь нажимает «назад», и теперь он показывает # Удалено_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);
49 голосов
/ 08 декабря 2014

Чтобы дать актуальный ответ на старый (но популярный) вопрос:

HTML5 представил методы history.pushState() и history.replaceState(), которые позволяют добавлять и изменять записи истории соответственно. Эти методы работают в сочетании с событием window.onpopstate.

Использование history.pushState() изменяет реферер, который используется в заголовке HTTP для XMLHttpRequest объектов, созданных после изменения состояния. Ссылка будет URL-адрес документа, окно которого this на момент создания объекта XMLHttpRequest.

Источник: Управление историей браузера из Mozilla Developer Network.

10 голосов
/ 10 июня 2013

Используя jQuery, я сделал простое решение:

$(window).on('hashchange', function() {
    top.location = '#main';
    // Eventually alert the user describing what happened
});

Пока что тестируется только в Google Chrome.

Это решило проблему для моего веб-приложения, которое также основано на AJAX.

Возможно, это немного хакерски - но я бы назвал это элегантным взломом ;-) Всякий раз, когда вы пытаетесь перемещаться назад, он добавляет хеш-часть в URI, технически то, что он затем пытается перейти назад. 1008 *

Перехватывает нажатие кнопки браузера и кнопки мыши. И вы не можете перебить его задом наперед, нажав несколько раз в секунду, что является проблемой, возникающей в решениях, основанных на setTimeout или setInterval.

8 голосов
/ 15 августа 2014

Я действительно ценю объяснение, данное в ответ Darkporter , но я думаю, что его можно улучшить, используя " hashchange " событие . Как объяснил darkporter, вы хотите убедиться, что все ваши кнопки изменяют значение window.location.hash.

  • Одним из способов является использование <button> элементов, а затем присоединение к ним событий, которые затем устанавливают window.location.hash = "#!somehashstring";.
  • Еще один способ сделать это - просто использовать ссылки для кнопок, например <a href="#!somehashstring">Button 1</a>. Хеш автоматически обновляется при нажатии на эти ссылки.

Причина, по которой после знака хеша у меня появляется восклицательный знак, заключается в том, чтобы удовлетворить парадигму "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.

5 голосов
/ 26 апреля 2016

Очень легко отключить кнопку НАЗАД браузера , как показано в приведенном ниже коде 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

Спасибо!

4 голосов
/ 04 декабря 2009

Из-за соображений конфиденциальности невозможно отключить кнопку «Назад» или просмотреть историю пользователя, но можно создавать новые записи в этой истории, не меняя страницы. Всякий раз, когда ваше AJAX-приложение меняет состояние, обновите top.location новым URI-фрагментом .

top.location = "#new-application-state";

Это создаст новую запись в стеке истории браузера. Некоторые библиотеки AJAX уже обрабатывают все скучные детали, такие как Really Simple History .

2 голосов
/ 06 июля 2017

Я разработал способ переопределения нормального поведения истории и создания отдельных событий кнопок 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, иначе вы его сломаете.

2 голосов
/ 30 августа 2015

В моей ситуации я хотел запретить кнопке «Назад» отправить пользователя на последнюю страницу и вместо этого предпринять другое действие. Используя событие 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 не сработает. В зависимости от вашей ситуации, это может работать и для вас.

1 голос
/ 03 ноября 2018

Кнопка назад в браузерах обычно пытается вернуться к предыдущему адресу. в 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('#', ''))
}
0 голосов
/ 28 сентября 2012

Я делаю что-то вроде этого. Я сохраняю массив с предыдущими состояниями приложения.

Инициировано как:

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