Как мне узнать, возникает ли событие popstate из действий назад или вперед с помощью pushstate HTML5?

Я разрабатываю веб-страницу, где в зависимости от следующих или обратных действий я выполняю соответствующую анимацию, проблема возникает при использовании pushstate. Когда я получаю событие, как мне узнать, нажал ли пользователь кнопки истории назад или вперед с помощью Pushstate API? Или мне нужно что-то реализовать самостоятельно?


person Davsket    schedule 23.01.2012    source источник
comment
Событие popstate говорит, пожалуйста, перейдите в это состояние. Похоже, что он предполагает, что вы знаете, в каком состоянии находитесь в данный момент, и, следовательно, что вам нужно сделать, чтобы изменить состояние.   -  person Neil    schedule 24.01.2012
comment
Проблема в том, что история - это стек, поэтому, если у меня есть список, и я иду вперед, вперед, вперед, назад, вперед, назад, вперед, а список будет: [1,2,3,4,5], история будет такой: [1,2,3,4,3,4,3,4]. В числах это просто, но с URL-адресами не так просто узнать, какой URL-адрес следующий, а какой - предыдущий.   -  person Davsket    schedule 24.01.2012
comment
Комментарий Беннедича ниже мне очень помог. Было бы неплохо принять его, чтобы он более эффективно помогал и другим.   -  person Gavin Anderegg    schedule 16.08.2013


Ответы (2)


Вы должны реализовать это самостоятельно, что довольно легко.

  • При вызове pushState присвойте объекту данных уникальный увеличивающийся идентификатор (uid).
  • Когда вызывается onpopstate обработчик; проверьте uid состояния на постоянную переменную, содержащую uid последнего состояния.
  • Обновите постоянную переменную с помощью uid текущего состояния.
  • Выполняйте разные действия в зависимости от того, был ли uid состояния больше или меньше uid последнего состояния.
person bennedich    schedule 24.01.2012
comment
Это хорошо работает, когда иерархия страниц / представлений четко определена, и всегда верно, что определенная страница следует за другой. Когда навигация может быть более динамичной, вы не можете полагаться на увеличивающийся UID. Я решил эту проблему, используя sessionStorage для поддержки стека последних X страниц. Затем в событии popstate вы можете проверить стек из sessionStorage, чтобы узнать, совпадает ли URL-адрес новой страницы с URL-адресом страницы N-2. Используйте sessionStorage вместо обычной переменной, чтобы она сохранялась между границами страницы. - person frontendbeauty; 31.10.2013
comment
@frontendbeauty Я думаю, вам следует опубликовать свое предложение в качестве ответа с фрагментом кода. - person andilabs; 30.09.2014
comment
кроме того, вместо того, чтобы проверять, больше / меньше значение, вы можете написать диспетчер изменения состояния, который проверяет идентификатор, чтобы узнать, какое текущее состояние и в какое состояние он переходит, а затем выполнить соответствующую анимацию - person Changbai Li; 13.04.2015
comment
Если это так просто, почему вы не написали реализацию? Я просто не понимаю, как сделать это без ошибок для любого возможного варианта использования. Независимо от того, где я сохраню текущее состояние, я могу придумать, как им злоупотреблять. - person Atomosk; 17.02.2018
comment
@frontendbeauty, что, если вы находитесь на странице, вы находитесь на средней странице b, и ваш стек выглядит так: [a, b, a] вы получаете popstate событие, как узнать, было ли оно назад или вперед? - person david_adler; 28.05.2021

Этот ответ должен работать с одностраничным приложением push-state, многостраничным приложением или их комбинацией. (Исправлено, чтобы исправить ошибку History.length, указанную в комментарии Мескалито.)

Как это работает

Мы можем легко прослушивать новые записи в стеке истории. Мы знаем, что для каждой новой записи спецификация требует, чтобы браузер:

  1. "Удалить все записи в истории сеансов контекста просмотра после текущей записи"
  2. «Добавить новую запись в конце»

Таким образом, в момент въезда:

новая позиция входа = позиция последнего показа + 1

Тогда решение:

  1. Пометить каждую запись истории собственной позицией в стеке
  2. Следите в хранилище сеансов за последней показанной позицией
  3. Узнайте направление движения, сравнив два

Пример кода

function reorient() // After travelling in the history stack
{
    const positionLastShown = Number( // If none, then zero
      sessionStorage.getItem( 'positionLastShown' ));
    let position = history.state; // Absolute position in stack
    if( position === null ) // Meaning a new entry on the stack
    {
        position = positionLastShown + 1; // Top of stack

        // (1) Stamp the entry with its own position in the stack
        history.replaceState( position, /*no title*/'' );
    }

    // (2) Keep track of the last position shown
    sessionStorage.setItem( 'positionLastShown', String(position) );

    // (3) Discover the direction of travel by comparing the two
    const direction = Math.sign( position - positionLastShown );
    console.log( 'Travel direction is ' + direction );
      // One of backward (-1), reload (0) and forward (1)
}

addEventListener( 'pageshow', reorient );
addEventListener( 'popstate', reorient ); // Travel in same page

См. Также живую копию кода.

Ограничение

Это решение игнорирует записи истории внешних страниц, чуждых приложению, как если бы пользователь никогда их не посещал. Он рассчитывает направление движения только по отношению к последней показанной странице приложения, независимо от любой внешней страницы, посещенной между ними. Если вы ожидаете, что пользователь поместит сторонние записи в стек (см. Комментарий Atomosk), вам может потребоваться обходной путь.

person Michael Allan    schedule 16.03.2018
comment
Выполните навигацию по вашему сайту, откройте внешнюю ссылку, дважды нажмите кнопку браузера «Назад», нажмите и удерживайте кнопку браузера «Вперед» и выберите внешнее хранилище сеансов сайта, нажмите «Назад». Может, я слишком нетерпеливый :). Также я думаю, что ваша функция может быть заменена на const direction = Math.sign(history.length - (sessionStorage.getItem('stateLastShown') || 0)); sessionStorage.setItem('stateLastShown', history.length);, поскольку вы всегда помещаете history.length в состояние истории. - person Atomosk; 19.03.2018
comment
@Atomosk (1) Вы правы, внешние ссылки не обрабатываются должным образом. Я добавил предупреждение об этом. (2) Я думаю, что ваш сокращенный код не сработает. History.length определяет позицию только для < i> новая запись в стек. Вам также понадобится код для обработки другого случая, который представляет собой измененную запись. - person Michael Allan; 20.03.2018
comment
Да вы правы. Я никогда не привыкну к тому, насколько плохой интерфейс API. Проверка того, как далеко я могу зайти с помощью кнопки назад / вперед, должна быть одной строкой, но кто-то решил иначе. - person Atomosk; 21.03.2018
comment
История браузера назад / вперед имеет фиксированный лимит, в Chrome - 50. Когда вы достигнете этого лимита, history.length всегда будет давать одно и то же число (50 в моем случае), поэтому это решение всегда будет возвращать 0 в качестве направления, потому что все записи состояния будут иметь идентичная позиция. - person Mesqalito; 28.01.2019
comment
@Mesqalito Вы правы, ограничения браузера на History.length делают его ненадежным. Я изменил ответ, чтобы вместо этого использовать переменную сеанса positionLastShown. - person Michael Allan; 09.05.2019