Comments 38
Завидую вам с вашими заказчиками.
Мне около полугода тому назад пришлось опять IETester ставить. Некоторые заказчики такие заказчики.
1. после двух history.back() вы вернетесь к самому первому состоянию, когда сайт был загружен.
2. Оно было заменено состоянием ({page: 3}, «title 3», "?page=3");

history.pushState({page: 2}, "title 2", "?page=2");

// заменить текущее состояние
history.replaceState({page: 3}, "title 3", "?page=3");
Нумерация страниц путает ужасно.

Поясните, пожалуйста, когда мы делаем back(), то переход идёт к первому запушенному состоянию?
Если так, то я всё ещё не понимаю, куда делась page 2?

И когда go(2) почему попадаем на page3?
back() — переход к предыдущему состоянию.

изначально имеем страницу «example.com/example.html». стек состояний пуст.

history.pushState({page: 1}, "title 1", "?page=1"); // location: http://example.com/example.html?page=1, state: {"page":1}
// теперь стек сотояний [null, {page: 1}]
history.pushState({page: 2}, "title 2", "?page=2"); // location: http://example.com/example.html?page=2, state: {"page":2}
// теперь стек сотояний [null, {page: 1}, {page: 2}]
history.replaceState({page: 3}, "title 3", "?page=3"); // location: http://example.com/example.html?page=3, state: {"page":3}
// здесь мы заменили текущее состояние, а не добавили его. поэтому грубо стек состояний таков:
[null, {page: 1}, {page:3}]

// Переходим назад по истории, получаем состояние {page: 1}
history.back(); // location: http://example.com/example.html?page=1, state: {"page":1}
// Переходим назад по истории, получаем состояние null
history.back(); // location: http://example.com/example.html, state: null
// Переходим на два состояния вперед, получаем состояние {page: 3}
history.go(2);  // location: http://example.com/example.html?page=3, state: {"page":3}
Я для себя давно написал маленький универсальный объект для работы с url.
Если браузер тянет History api — работаем с ним, если нет — то с hash.

Вот, может кому-то еще пригодится. Использовать просто:
Hash.add('key1', 'var1'); // добавляем значение в url
Hash.remove('key1'); // удаляем 

Hash.set({'key1':'var1'}); // заменяем все значения своим массивом
Hash.get(); // получаем данные в url массивом
Hash.clear(); // удаляем все значения в url без перезагрузки

Рабочий пример — просто покликайте по фильтру.
Странно кликаю по фильтру норм и ссылка меняется и содержимое, щелкаю назад меняется только ссылка содержимое как было так и осталось.
Видимо это оттого, что у меня нет обработки back/forward
window.onpopstate = function(event) {
  console.log("location: " + location.href + ", state: " + JSON.stringify(event.state));
};

Постараюсь разобраться с этим, как появится немного времени.
Есть готовые полифилы HTML5-History-API от devote, мой форк, которые делают тоже самое, но только прозрачно для пользователя. У себя в форке я ещё добавил событие pagechange которое срабатывает и на методы pushState/replaceState, и на событие popstate
Если вы пушите событие (первый параметр), то могли бы пояснить, как он может использоваться, благо работает он как положено. А вот второй параметр (title), не работает для большинства браузеров, title страницы надо вручную менять через document.title чтобы работало как положено — опять же не помешает добавить в статью.
А вот второй параметр (title), не работает для большинства браузеров, title страницы надо вручную менять через document.title чтобы работало как положено


Ну это не проблема… Исходя из примера:

// Обработчик back/forward событий
window.onpopstate = function(event) {
  console.log("location: " + location.href + ", state: " + JSON.stringify(event.state));
  // меняем title
  document.title = event.state.title;
};

// добавить состояние истории
history.pushState({page: 1, title: "title 1"}, "", "?page=1");
history.pushState({page: 2, title: "title 2"}, "", "?page=2");

// заменить текущее состояние
history.replaceState({page: 3, title: "title 3"}, "", "?page=3");
Все верно, осталось только дописать как хранить динамический контент в event.state, чтобы не перезагружать страницу при нажатиях back и forward, потом убедить автора добавить это в статью и лично я буду абсолютно доволен :)
осталось только дописать как хранить динамический контент в event.state


Не самый лучший вариант, но тоже имеет право на жизнь :)

<html>
<head>
  <title>Some Title</title>
</head>
<body>
  <a href="?page=1" title="Title 1">Page 1</a>
  <a href="?page=2" title="Title 2">Page 2</a>
  <a href="?page=3">Page 3</a>

  <script>
    window.onpopstate = function (event) {
      document.title = event.state.title;
    };
    var a = document.querySelectorAll('a');
    for (var i=0; i < a.length; i++) {
      a[i].onclick = function() {
        var title = document.title = this.title ? this.title : this.innerHTML;
        history.pushState({title: title}, "", this.href);
          return false;
        }
      }
    </script>
</body>
</html>
Вы либо меня не поняли, либо упростили пример так, что потерялась сама идея, хранить в первом параметре состояние\контент динамического блока\блоков (того что вы аяксом грузите н-р).

И к слову вместо ?page=1 и ?page=2, можно сделать красивые /page1 и /page2 (нужно будет чутка только в .htaccess подшаманить, чтоб букмарки открывались так, как ожидает пользователь), главная идея статьи избавиться от hash, так что почему не сделать симпатичный URL, вообще без параметров сходу.

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


И к слову вместо ?page=1 и ?page=2, можно сделать красивые /page1 и /page2
Это понятно… ?page=1 и ?page=2 взяты из примера самой статьи

нужно будет чутка только в .htaccess подшаманить, чтоб букмарки открывались так, как ожидает пользователь
Пишу на python/django по-этому ничего шаманить не надо

Я может придираюсь через чур, но как заметили ниже, статья введение на хабре уже была, раз уж это уже вторая о том же, можно было бы довести до полезного на практике примера.
с этим я согласен… но акцент был на поддержку браузеров (то бишь даже ie поддерживает)
А вот второй параметр (title), не работает для большинства браузеров, title страницы надо вручную менять через document.title чтобы работало как положено

Если внимательно прочесть спецификацию, второй параметр никак не относиться к title страницы. И говорить о том что он не работает, не совсем правильно.

Суть данного параметра в том, что многие привыкли читать название параметра как title и воспринимать его как title-страницы (грубо сопоставлять с document.title), что является ошибочным. Этот параметр не имеет ничего общего с document.title. Он нужен для того что бы менять имя страницы в истории. Для примера это имя вы можете увидеть при продолжительном нажатии на кнопки back/forward, то есть попробуйте нажать левой клавишей мыши на одну из этих стрелок с небольшой задержкой, откроется список, вот в нем и будут отображаться те самые имена что были занесены во второй параметр.
Для примера это имя вы можете увидеть при продолжительном нажатии на кнопки back/forward, то есть попробуйте нажать левой клавишей мыши на одну из этих стрелок с небольшой задержкой, откроется список, вот в нем и будут отображаться те самые имена что были занесены во второй параметр.

Параметр title никак к этому не относится!
В историю вписывается то что в: <title>Some Title</title>
или в случае с javascript'ом: document.title = "Some Title";
вы так утверждаете лишь потому что браузеры в реальности пишут туда заголовок из document.title, хотя в реальности так работать не должно (то есть должно но не в случае с pushState/replaceState). Хотя например Opera 12 (если моя память не изменяет) пишет то что попадает вторым параметром в pushState/replaceState, то есть поступает правильно в отличии от других браузеров.

попробуйте открыть в Опере 12 консоль и вбить:
history.pushState(null, 'Новый заголовок');
history.pushState(null, 'Новый заголовок 2', '/urlurl');

После проделайте то о чем я писал в посте ранее
Сорри… Да, в Опере 12 работает.
До этого тестил в Хроме, там это не работает, по этому дал такой ответ.
Стоит отметить что onpopstate в хроме может вызывается при подписываниии (может тк если обернуть в setTimeout, то результат может быть не предсказуем).

А так для старых браузеров я просто перегружал страницу через window.location = '/url' и имел отличную возможность использовать hash для всякого рода сообщений и выделений внутри страницы.
В этой статье нет ни слова о onpopstate, который является неотъемлемой частью History API.
Но в любом случае хорошо, что она упомянута в комментариях.
Простите, но:
// Обработчик back/forward событий
window.onpopstate = function(event) {
В статьи не озвучено:
* полифилы: HTML5-History-API от devote, мой форк (используется у нас в компании)
* подводные камни при использовании History-API:
  1. разное срабатывание onpopstate при первой загрузке страницы в Webkit/Blink и FF/Opera
  2. забагованые location.[pathname/href] в старых Chrome/Safari (в том числе на старых Android/iPhone) и Opera 12-: эти браузеры разэкранируют спец символы, при получении значений этих свойств, в отличии от последних версий браузеров
  3. Баги Opera 12- с релативными ссылками при использовании History-API: после второго применения History-API node.href указывает на урл, сформированный из предыдущего установленного через History-API значения и относительной ссылки самого элемента a
  4. Баг в последнем Webkit/Blink с History-API + Fullscreen API: Chromium/Safari выходят из полноэкранного режима (активированного через Fullscreen API) при использовании History-API

В своём форке я исправляю все эти проблемы.

В общем, использование History-API это не панацея и нужно было ещё допиливать API браузера для реального использования в крупном проекте.
5. Забыл добавить, что в общем-то, использование History-API не такое удобное как хотелось бы. Т.к. при использовании методов pushState/replaceState не происходит никакого события, то остальная часть приложения ничего не знает о том, что урл сменился. Поэтому в своём форке я добавил событие
pagechange = {
  newUrl: string
  , oldUrl: string
  , popstate: boolean
}

диспатчится оно на window. Срабатывает и на методы pushState/replaceState и на событие popstate
Вот только с popstate у Chrome до сих пор проблемы. Приходится изобретать разнообразные велосипеды, чтобы оно работало как следует.
Проблем хватает у многих браузерах, пока еще ни один браузер идеально не поддерживает History-API поэтому приходиться прикручивать костыли для тех или иных случаев. В моей реализации библиотеки, о которой упомянул termi я стараюсь избавиться от подобных багов и стабилизировать работу API для всех браузеров одинаково.
Внезапный вывод. «Пользуйтесь современной технологией, давно пора! Правда придётся костыли ваять на каждый браузер...»
У каждой технологии — свои предназначения, и если на сайтах используется ajax + поддержка IE9+ only, то в данном случае статья верная, но если разрабатывается приложение, ну, допустим под VK, под FB, под мэил.ру или одноклассники уж на худой конец, то тут Вы ошибаетесь просто катастрофически, просто потому, что History API просто не работает в айфреймах, и не важно какой браузер.
Никто и не говорит о приложениях под соцсети, поэтому фраза «тут Вы ошибаетесь» не думаю что уместна. Там свои причуды которые нужно выносить в отдельную статью и обсуждения.
В заголовке статьи и в самой статье описано, что location.hash для AJAX — на свалку истории, хистори апи уже ворвался в нашу жизнь — используйте его. Это если в кратце. Я уточнил, что да, это всё хорошо, но не стоит быть столь категоричным.

Может немного резковато получилось, да.
Хотя бы как минимум что бы можно было ходить по истории. Хотя конечно для этих целей можно использовать и location.hash. Но все же на мой взгляд лучше использовать более новые технологии
Так ведь вперед-назад во фреймах все равно работают непонятно как.
Так ведь непонятно (с точки зрения пользователя) когда должен отрабатывать верхний уровень, а когда нижний.
Only those users with full accounts are able to leave comments. Log in, please.