Адрес страницы вперед javascript history forward 1. Используем HTML5 History API. Назначение объекта history

На iOS - iPhone, iPod touch 26.03.2019
На iOS - iPhone, iPod touch

Using history.pushState() changes the referrer that gets used in the HTTP header for XMLHttpRequest objects created after you change the state. The referrer will be the URL of the document whose window is this at the time of creation of the XMLHttpRequest object.

Example of pushState() method

Suppose http://mozilla.org/foo.html executes the following JavaScript:

Let stateObj = { foo: "bar", }; history.pushState(stateObj, "page 2", "bar.html");

This will cause the URL bar to display http://mozilla.org/bar.html , but won"t cause the browser to load bar.html or even check that bar.html exists.

Suppose now that the user navigates to http://google.com, then clicks the Back button. At this point, the URL bar will display http://mozilla.org/bar.html and history.state will contain the stateObj . The popstate event won"t be fired because the page has been reloaded. The page itself will look like bar.html.

If we click Back again, the URL will change to http://mozilla.org/foo.html , and the document will get a popstate event, this time with a null state object. Here too, going back doesn"t change the document"s contents from what they were in the previous step, although the document might update its contents manually upon receiving the popstate event.

The pushState() method

pushState() takes three parameters: a state object, a title (which is currently ignored), and (optionally) a URL. Let"s examine each of these three parameters in more detail:

    state object - The state object is a JavaScript object which is associated with the new history entry created by pushState() . Whenever the user navigates to the new state, a popstate event is fired, and the state property of the event contains a copy of the history entry"s state object.

    The state object can be anything that can be serialized. Because Firefox saves state objects to the user"s disk so they can be restored after the user restarts the browser, we impose a size limit of 640k characters on the serialized representation of a state object. If you pass a state object whose serialized representation is larger than this to pushState() , the method will throw an exception. If you need more space than this, you"re encouraged to use sessionStorage and/or localStorage .

    title - Firefox currently ignores this parameter, although it may use it in the future. Passing the empty string here should be safe against future changes to the method. Alternatively, you could pass a short title for the state to which you"re moving.

    URL - The new history entry"s URL is given by this parameter. Note that the browser won"t attempt to load this URL after a call to pushState() , but it might attempt to load the URL later, for instance after the user restarts the browser. The new URL does not need to be absolute; if it"s relative, it"s resolved relative to the current URL. The new URL must be of the same origin as the current URL; otherwise, pushState() will throw an exception. This parameter is optional; if it isn"t specified, it"s set to the document"s current URL.

Note: In Gecko 2.0 (Firefox 4 / Thunderbird 3.3 / SeaMonkey 2.1) through Gecko 5.0 (Firefox 5.0 / Thunderbird 5.0 / SeaMonkey 2.2), the passed object is serialized using JSON. Starting in Gecko 6.0 (Firefox 6.0 / Thunderbird 6.0 / SeaMonkey 2.3), the object is serialized using the structured clone algorithm . This allows a wider variety of objects to be safely passed.

In a sense, calling pushState() is similar to setting window.location = "#foo" , in that both will also create and activate another history entry associated with the current document. But pushState() has a few advantages:

  • The new URL can be any URL in the same origin as the current URL. In contrast, setting window.location keeps you at the same document only if you modify only the hash.
  • You don"t have to change the URL if you don"t want to. In contrast, setting window.location = "#foo"; creates a new history entry only if the current hash isn"t #foo .
  • You can associate arbitrary data with your new history entry. With the hash-based approach, you need to encode all of the relevant data into a short string.
  • If title is subsequently used by browsers, this data can be utilized (independent of, say, the hash).

Note that pushState() never causes a hashchange event to be fired, even if the new URL differs from the old URL only in its hash.

Specifications Specification Status Comment
HTML Living Standard
Living Standard No change from HTML5 .
HTML5
The definition of "History" in that specification.
Recommendation Initial definition.
Browser compatibility

The compatibility table on this page is generated from structured data. If you"d like to contribute to the data, please check out https://github.com/mdn/browser-compat-data and send us a pull request.

Update compatibility data on GitHub

Desktop Mobile Chrome Edge Firefox Internet Explorer Opera Safari Android webview Chrome for Android Edge Mobile Firefox for Android Opera for Android Safari on iOS Samsung Internet History back forward go length pushState replaceState scrollRestoration state
Chrome Full support Yes Edge Full support Yes Firefox Full support Yes IE Full support Yes Opera Full support Yes Safari Full support Yes Samsung Internet Android ?
Chrome Full support Yes Edge Full support 12 Firefox Full support Yes IE ? Opera ? Safari ? WebView Android Full support Yes Chrome Android Full support Yes Edge Mobile ? Firefox Android Full support Yes Opera Android ? Safari iOS ? Samsung Internet Android ?
Chrome Full support Yes Edge Full support 12 Firefox Full support Yes IE ? Opera ? Safari ? WebView Android Full support Yes Chrome Android Full support Yes Edge Mobile ? Firefox Android Full support Yes Opera Android ? Safari iOS ? Samsung Internet Android ?
Chrome Full support Yes Edge Full support 12 Firefox Full support Yes IE ? Opera ? Safari ? WebView Android Full support Yes Chrome Android Full support Yes Edge Mobile ? Firefox Android Full support Yes Opera Android ? Safari iOS ? Samsung Internet Android ?
Chrome Full support Yes Edge Full support 12 Firefox Full support Yes IE ? Opera ? Safari ? WebView Android Full support Yes Chrome Android Full support Yes Edge Mobile ? Firefox Android Full support Yes Opera Android ? Safari iOS ? Samsung Internet Android ?
Chrome Full support 5 Edge Full support 12 Firefox Full support 4

Notes

Full support 4

Notes

Notes
IE Full support 10 Opera Full support 11.5 Safari Full support 5 WebView Android Full support Yes Chrome Android Full support Yes Edge Mobile Full support Yes Firefox Android Full support Yes Opera Android Full support Yes Samsung Internet Android ?
Chrome Full support 5 Edge Full support 12 Firefox Full support 4

Notes

Full support 4

Notes

Notes In Firefox 2 through 5, the passed object is serialized using JSON. Starting in Firefox 6, the object is serialized using . This allows a wider variety of objects to be safely passed.
IE Full support 10 Opera Full support 11.5 Safari Full support 5 WebView Android Full support Yes Chrome Android Full support Yes Edge Mobile Full support Yes Firefox Android Full support Yes Opera Android Full support Yes Safari iOS Full support 4.3 Samsung Internet Android ?
Chrome Full support 46 Edge No support No Firefox Full support 46 IE No support No Opera Full support 33 Safari Full support Yes WebView Android No support No Chrome Android Full support 46 Edge Mobile No support No Firefox Android Full support Yes Opera Android Full support Yes Safari iOS Full support Yes Samsung Internet Android ?
Chrome Full support Yes Edge Full support 12 Firefox Full support Yes IE ? Opera ? Safari ? WebView Android Full support Yes Chrome Android Full support Yes Edge Mobile ? Firefox Android Full support Yes Opera Android ? Safari iOS ? Samsung Internet Android ?
Legend Full support Full support No support No support Compatibility unknown Compatibility unknown See implementation notes. See implementation notes.

16 ответов

Короткий ответ: вы не можете.

Технически существует точный способ проверки подлинности:

History.previous

Однако это не сработает. Проблема в том, что в большинстве браузеров это считается нарушением безопасности и обычно просто возвращает undefined .. p >

History.length

Это свойство, которое другие предложили...
Однако длина не работает полностью, поскольку в истории вы не указываете , где . Кроме того, он не всегда начинается с того же номера. Например, браузер, не настроенный на целевую страницу, начинается с 0, тогда как другой браузер, который использует страницу перевязки, начинается с 1.

History.back();

History.go(-1);

и он просто ожидал, что если вы не сможете вернуться, то щелчок по ссылке ничего не делает.

Мой код позволяет браузеру вернуться на одну страницу, и если это не удается, он загружает резервный URL-адрес. Он также обнаруживает изменения хэштегов.

Если кнопка "Назад" недоступна, резервный url будет загружен через 500 мс, поэтому браузер имеет достаточно времени для загрузки предыдущей страницы. Загрузка обратного URL сразу после window.history.go(-1); приведет к тому, что браузер будет использовать резервный URL-адрес, потому что js script еще не остановился.

Function historyBackWFallback(fallbackUrl) { fallbackUrl = fallbackUrl || "/"; var prevPage = window.location.href; window.history.go(-1); setTimeout(function(){ if (window.location.href == prevPage) { window.location.href = fallbackUrl; } }, 500); }

это похоже на трюк:

Function goBackOrClose() { window.history.back(); window.close(); //or if you are not interested in closing the window, do something else here //e.g. theBrowserCantGoBack(); }

Вызов history.back(), а затем window.close(). Если браузер сможет вернуться в историю, он не сможет перейти к следующему утверждению. Если он не сможет вернуться, он закроет окно.

Однако, обратите внимание, что если страница была достигнута, набрав URL-адрес, то firefox не позволит script закрыть окно.

Вы не можете напрямую проверить, доступна ли кнопка "Назад". Вы можете посмотреть history.length>0 , но это будет верно, если страницы впереди и на текущей странице. Вы можете только быть уверены, что кнопка "Назад" непригодна, если history.length===0 .

Если это не достаточно хорошо, обо всем, что вы можете сделать, это позвонить history.back() и, если ваша страница все еще загружена, кнопка "Назад" недоступна! Конечно, это означает, что если кнопка "Назад" доступна, вы просто перешли от страницы. Вам не разрешено отменять навигацию в onunload , поэтому обо всем, что вы можете сделать, чтобы остановить обратное на самом деле, это вернуть что-то из onbeforeunload , что приведет к появлению большого раздражающего приглашения. Это не стоит.

На самом деле обычно это действительно плохая идея делать что-либо с историей. Навигация по истории - для браузера Chrome, а не для веб-страниц. Добавление ссылок "вернуться" обычно вызывает больше недоумения пользователей, чем это стоит.

Вот как я это сделал.

Я использовал событие "beforeunload" , чтобы установить логическое значение. Затем я установил тайм-аут, чтобы посмотреть, запускается ли "beforeunload".

Var $window = $(window), $trigger = $(".select_your_link"), fallback = "your_fallback_url"; hasHistory = false; $window.on("beforeunload", function(){ hasHistory = true; }); $trigger.on("click", function(){ window.history.go(-1); setTimeout(function(){ if (!hasHistory){ window.location.href = fallback; } }, 200); return false; });

Кажется, он работает в основных браузерах (тестировал FF, Chrome, IE11 до сих пор).

В моих проектах есть фрагмент:

Function back(url) { if (history.length > 2) { // if history is not empty, go back: window.History.back(); } else if (url) { // go to specified fallback url: window.History.replaceState(null, null, url); } else { // go home: window.History.replaceState(null, null, "/"); } }

К вашему сведению: я использую History.js для управления историей браузера.

Зачем сравнивать history.length с номером 2?

Потому что стартовая страница Chrome считается первым элементом в истории браузера.

Существует несколько возможностей history.length и поведения пользователя:

  • Пользователь открывает новую пустую вкладку в браузере, а затем запускает страницу. history.length = 2 и мы хотим отключить back() в этом случае, потому что пользователь перейдет на пустую вкладку.
  • Пользователь открывает страницу в новой вкладке , щелкая ссылку где-то раньше. history.length = 1 и снова мы хотим отключить метод back() .
  • И, наконец, пользователь попадает на текущую страницу после перезагрузки нескольких страниц . history.length > 2 и теперь back() можно включить.

Примечание: я пропускаю случай, когда пользователь попадает на текущую страницу после нажатия на ссылку с внешнего сайта без target="_blank" .

Примечание 2: document.referrer пуст, когда вы открываете веб-сайт, вводя его адрес, а также когда веб-сайт использует ajax для загрузки подстраниц, поэтому я прекратил проверку этого значения в первом случае.

history.length бесполезен, так как он не показывает, может ли пользователь вернуться в историю. Также разные браузеры используют начальные значения 0 или 1 - это зависит от браузера.

Рабочее решение - использовать событие $(window).on("beforeunload" , но я не уверен, что он будет работать, если страница загружается через ajax и использует pushState для изменения истории окна.

Итак, я использовал следующее решение:

Var currentUrl = window.location.href; window.history.back(); setTimeout(function(){ // if location was not changed in 100 ms, then there is no history back if(currentUrl === window.location.href){ // redirect to site root window.location.href = "/"; } }, 100);

Будьте осторожны с window.history.length потому что оно также содержит записи для window.history.forward()

Таким образом, вы можете иметь window.history.length с более чем 1 записями, но без записей истории. Это означает, что ничего не произойдет, если вы запустите window.history.back()

Я придумал следующий подход. Он использует событие onbeforeunload, чтобы определить, покидает ли браузер страницу или нет. Если в определенный промежуток времени он не будет перенаправлен на резервную копию.

Var goBack = function goBack(fallback){ var useFallback = true; window.addEventListener("beforeunload", function(){ useFallback = false; }); window.history.back(); setTimeout(function(){ if (useFallback){ window.location.href = fallback; } }, 100); }

Вы можете вызвать эту функцию, используя goBack("fallback.example.org") .

window.location.pathname выдаст вам текущий URI. Например, https://domain/question/1234/i-have-a-problem выдаст /question/1234/i-have-a-problem . См. Документацию о window.location

Затем вызов функции split() даст нам все фрагменты этого URI. поэтому, если мы возьмем наш предыдущий URI, у нас будет что-то вроде ["", "question", "1234", "i-have-a-problem"] . См. Документацию о String.prototype.split() для получения дополнительной информации.

Вызов filter() здесь для того, чтобы отфильтровать пустую строку, созданную обратной косой чертой. Он будет возвращать только фрагмент URI, длина которого больше 1 (непустая строка). Таким образом, у нас будет что-то вроде ["question", "1234", "i-have-a-question"] . Это можно было бы написать так:

"use strict"; window.location.pathname.split("/").filter(function(fragment) { return fragment.length > 0; });

См. Документацию о Array.prototype.filter() и назначении Destructuring для получения дополнительной информации.

Теперь, если пользователь пытается вернуться, находясь на https://domain/ , мы не будем запускать оператор if и поэтому не window.history.back() вызывать метод window.history.back() чтобы пользователь оставался на нашем веб-сайте. Этот URL будет эквивалентен который имеет длину 0 , а 0 > 0 - false. Следовательно, молча терпит неудачу. Конечно, вы можете что-то зарегистрировать или выполнить другое действие, если хотите.

"use strict"; function previousPage() { if (window.location.pathname.split("/").filter(({ length }) => length > 0).length > 0) { window.history.back(); } else { alert("You cannot go back any further..."); } }

Ограничения

Конечно, это решение не будет работать, если браузер не поддерживает History API . Проверьте документацию, чтобы узнать больше об этом, прежде чем использовать это решение.

Данная статья раскрывает предназначение HTML5 History API. До появления данного API мы часто использовали значение хеш-функций, для работы с тяжелыми, одностраничными приложениями, потому что изменение электронного адреса не было возможно без обновления страницы. Кроме того, когда вы изменяете значение хеш-функций для электронного адреса никакие изменения не вносятся в историю браузера.

Однако теперь, обе операции доступны благодаря HTML5 History API. Теперь мы можем создавать одностраничные приложения, не применяя значение хеш-функций. Это также позволяет нам создавать приложения благоприятно совместимые с SEO. Кроме того, данная техника позволяет уменьшать трафик - но как?

В этой статье займёмся разработкой одностраничного приложения. Для этого загрузим все необходимые ресурсы на первой странице. Это позволит нам в дальнейшем подгружать только нужный контент. Другими словами, во время последующих запросов, вместо того, чтобы постоянно загружать все ресурсы, приложение загрузит только необходимую информацию.

Отправка контента должна осуществляться со стороны сервера.

Браузерная поддержка

На время написания данной статьи HTML5 History API довольно-таки неплохо поддерживается браузерами. Однако, в самом начале скрипта мы создадим проверку. Это позволит проверить браузерную совместимость.

Чтобы определить программным путем, поддерживает ли ваш браузер API, используйте следующую проверку:

Return !!(window.history && history.pushState);

Если вы используете Modernizr, то проверка будет следующей:

If (Modernizr.history) { // History API Supported }

Если ваш браузер не поддерживает History API, то вы можете использовать полизаполнение history.js .

Взаимодействие с историей
  • history.pushState()
  • history.replaceState()
  • С помощью данных функций можно добавлять и обновлять состояние истории. Они работают почти одинаково и принимают один и тот же же набор параметров. В дополнение, можно упоминуть ещё об одной функции: popstate . Чуть позже мы увидим их в действии.

    Функции pushState и replaceState принимают одинаковое число параметров:

    • state строка в формате JSON.
    • title на данный момент игнорируется всеми браузерами поэтому его лучше установить как null.
    • url представляет собой любой электронный адрес. Данное значение будет обновлено в браузере. Нет разницы, существует данный URL или нет. Самое главное, что страница не будет перезагружена.

    Различия между данными методами состоит в том, что pushState добавит новую запись в историю, а replaceState заменит текущее значение. Давай рассмотрим работу данных функций на примере.

    Предположим у нас есть несколько блоков, расположенных в стеке, названные 1 и 2. Так же у нас есть блок 3. Когда мы выполним команду pushState , блок 3 добится в существующий стек. В результате в нём будет 3 блока.

    Теперь представьте ту же ситуацию. При выполнении функции replaceState , блок 2 будет заменён на блок 3. Таким образом количество блоков останется тем же.

    Наглядный пример:

    Метот popstate будет запускаться, при передвижении по истории, используя методы history.go или history.back . В браузерах WebKit команда popstate запускалась бы после события onload , но это не касается Firefox и IE.

    Примеры: HTML

    Click on Links above to see history API usage using pushState method. Home!

    Lorem Ipsum is simply dummy text of the printing and typesetting industry.

    JavaScript

    jQuery("document").ready(function(){ jQuery(".historyAPI").on("click", function(e){ e.preventDefault(); var href = $(this).attr("href"); // Getting Content getContent(href, true); jQuery(".historyAPI").removeClass("active"); $(this).addClass("active"); }); }); // Adding popstate event listener to handle browser back button window.addEventListener("popstate", function(e) { // Get State value using e.state getContent(location.pathname, false); }); function getContent(url, addEntry) { $.get(url) .done(function(data) { // Updating Content on Page $("#contentHolder").html(data); if(addEntry == true) { // Add History Entry using pushState history.pushState(null, null, url); } }); }

    Здравствуйте, уважаемые читатели a! Сегодняшняя статья будет посвящена работе с HTML5 History API . В прошлых версиях HTML у нас был ограниченный контроль над историей браузера. Мы могли перемещаться по истории вперед и назад с помощью доступных методов. Теперь, благодаря HTML5 History API, у нас есть больше возможностей использования истории браузера пользователя: можно «гулять» по истории, добавлять свои элементы в историю и обновлять текущую запись в истории.

    Что это?

    HTML5 History API представляет собой стандартизированный способ манипулировать историей браузера с помощью . Часть этого API — навигация по истории, была доступна в предыдущих версиях HTML. В HTML5 включили возможность добавлять записи в историю браузера, чтобы визуально изменить URL в адресной строке браузера (без обновления страницы) и заменять текущую запись в истории браузера. Это означает, что URL в адресной строке браузера так и остается уникальным идентификатором для текущего ресурса, даже в приложениях с большим количеством скриптов, которые зачастую не выполняют полного обновления страницы.

    Поддержка браузеров

    На текущий момент данный API поддерживается множеством браузеров. Более подробно Вы можете узнать об этом .

    Чтобы программно определить, поддерживается ли API, используем:

    Если браузер не поддерживает API или Вы ищете кроссбраузерное решение, то можно использовать плагин history.js . Построен на основе HTML5 History API и использует те же самые методы, в то же время можно не беспокоиться о работоспособности в старых браузерах.

    Методы и свойства API

    Простой пример использования

    Давайте рассмотрим на примере работу с методом pushState . Допустим имеются 3 страницы почти с одинаковым содержанием. Разметка выглядит следующим образом:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14



    Главная
    О нас
    Контакты


    Нажмите на ссылки выше, чтобы использовать метод pushState .

    Главная!
    Здесь какой-то текст главной страницы.


    Вот как мы будем обрабатывать клики в меню:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31

    $("document" ) .ready (function () {
    $(".historyAPI" ) .on ("click" , function (e) {
    // отменяем стандартное действие при клике
    e.preventDefault () ;
    // Получаем адрес страницы
    var href = $(this ) .attr ("href" ) ;
    // Передаем адрес страницы в функцию
    getContent(href, true ) ;
    } ) ;
    } ) ;

    // Добавляем обработчик события popstate,
    // происходящего при нажатии на кнопку назад/вперед в браузере
    window.addEventListener ("popstate" , function (e) {
    // Передаем текущий URL
    getContent(location.pathname , false ) ;
    } ) ;

    // Функция загрузки контента
    function getContent(url, addEntry) {
    $.get (url) .done (function (data) {
    // Обновление только текстового содержимого в сером блоке
    $("#contentHolder" ) .html ($(data) .find ("#contentHolder" ) .html () ) ;
    // Если был выполнен клик в меню - добавляем запись в стек истории сеанса
    // Если была нажата кнопка назад/вперед, добавлять записи в историю не надо
    if (addEntry == true ) {
    // Добавляем запись в историю, используя pushState
    history.pushState (null , null , url) ;
    }
    } ) ;
    }

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

    Until recently, we developers couldn’t to do much with the state and history of the browser. We could check the number of items in the history and push users forwards and backwards, but this provides little benefit to the user. With the rise of more dynamic web pages, we need more control. Thankfully, HTML5 gives us that control by extending the JavaScript History API.

    What’s the point?

    It goes without saying that URLs are important. They’re the method of accessing the vast collections of information and resources on the web, and more recently, they’ve begun representing the intended state of a web application. You can copy these URLs and share them with your friends or use them to create links from any HTML document. They’re the veins of the web, and they need to be looked after.

    Previously, the JavaScript History API offered some very simple functionality:

    // Check the length of the history stack console.log(history.length); // Send the user agent forward console.log(history.forward()); // Send the user agent back console.log(history.back()); // Send the user agent back (negative) or forward (positive) // by a given number of items console.log(history.go(-3));

    With dynamic Ajax web applications, where the browser updates the page in parts instead of changing location entirely, it’s difficult to give the user a URL to bookmark or share the current application state. Fragment identifiers , like those used on this article’s headings via the id attribute, provide some state information, but they’re entirely dependent on client-side scripts.

    Save this file and open it in your favourite editor. It must be accessed via HTTP, so that means you need either a local server (e.g. http://localhost/) or an online web server you can upload to. Viewing the file directly using your browser’s Open File function will not work , since it uses the file:// protocol and not HTTP. Also be sure to update the href attributes on each of the navigation links to ensure the correct directory structure is used. Personally, I’m viewing the demo locally at http://localhost/history .

    We’ll be working exclusively within the element at the end of the . The code includes some simple styles and dynamically changes the content as you click the links. In reality, this could be loaded from your server via XMLHttpRequest , but for the purposes of this demonstration I’ve bundled it up into a self-contained file. The important part is that we have a quick-and-dirty dynamic page to work with, so let the fun begin!

    At the moment there, is no bookmarkable URL for the different states of this page. If you click around the navigation items, then click Back in your browser, you won’t be taken back to the previous state and may even be taken away from the page to whatever you viewed before (depending on your browser). It would be nice if you could share “Socks” with your friends, right? We can do that via history.pushState() .

    The history.pushState() method takes three parameters:

    Data Some structured data, such as settings or content, assigned to the history item. title The name of the item in the history drop-down shown by the browser’s back and forward buttons. (Note: this is not currently supported by any major browsers.) url (optional) The URL to this state that should be displayed in the address bar.

    With these parameters, you can define the state of the page, give that state a name, and even provide a bookmarkable address, as if the page had reloaded entirely. Let’s dive right in and add this to the clickHandler function, right above the return statement:

    Function clickHandler(e) { /* Snip... */ // Add an item to the history log history.pushState(data, event.target.textContent, event.target.href); return event.preventDefault(); }

    The single line of code we added informs the history object that:

    • we want to add an item to the log,
    • it should remember the data that we’ve already loaded,
    • it should assign a name to this state based on the text of the link we clicked (even though this isn’t used - it’s good to get into the habit of recording a name for the state), and
    • it should update the address bar with the href attribute of that link.

    Reload the page in your browser and click a few of the links, keeping an eye on the address bar. Notice how it changes on each click, despite the fact that you aren’t actually navigating away from this page. If you also have a look at your history log, you’ll see a long list of page titles (in this case ”Kittens!” over and over). Provided your server is set up to serve the correct page upon access, the user could copy that URL and paste it into a new browser window to jump straight to that kitten.

    At the moment, clicking the back button will pop you through the history items, but the page won’t react to these changes. That’s because so far, we’ve only created the history records. How can we allow active users to return to a previous state? We listen to the popstate event.

    Historical Events in Navigation

    The user agent fires a popstate event when the user navigates through their history, whether backwards or forwards, provided it isn’t taking the user away from the current page. That is, all those pushState s we called will keep the user on the current page, so the popstate event will fire for each history item they pop through.

    Before the closing tag, add a new listener for the popstate event:

    // Revert to a previously saved state window.addEventListener("popstate", function(event) { console.log("popstate fired!"); updateContent(event.state); });

    We attach the event listener to the window , which is responsible for firing the event, and pass this event into our handler. We log a message (so we can see when this event is firing), and then we update the content using the state we saved previously. The state is attached to the event object via the state property.

    Open up the page fresh in your browser, click around like before, and then click back. As before, the URL in the address bar changes as you cycle through states, but now the content is also restored back to what it should be. Click forward, and the content is likewise correctly restored.

    If you look at the developer console in Chrome when you load the page for the first time, you’ll see the popstate event fired immediately, before you’ve even clicked a link. This is because Chrome considers the initial page load to be a change in state, and so it fires the event. In this instance, the state property is null , but thankfully the updateContent function deals with this. Keep this in mind when developing as it could catch you out, especially if other browsers assume this behavior.

    Rewriting history

    Unfortunately, as fantastic as HTML5 is, it doesn’t allow us actual time travel. If it did, I would be going back to my childhood and telling a younger me, “Yes, you should have a slice of cake”. Take that as you will.

    The History API does, however, allow us to make amends to our history log items. For example, we could update the current state in response to fresh user input in a form. We can do this with history.replaceState .

    replaceState works just as pushState does, with the exact same parameters, except that it updates the current entry instead of adding a new one. I can think of one situation in our demo where this could be used: the initial page load. If you click back for long enough, you’ll find that going back to the original URL doesn’t provide you the original content. Let’s fix that by adding the following to the bottom of our script:

    // Store the initial content so we can revisit it later history.replaceState({ content: contentEl.textContent, photo: photoEl.src }, document.title, document.location.href);

    As this runs when the page loads, it saves the initial page state. We can later load this state when the user browses back to this point via the event listener we set up previously. You can try it out by loading up the page, clicking a few links, and then hitting back until you return to the original URL. The initial content has returned!

    Demo

    I’ve set up a demo of our completed code. I’ve also added a little back-end magic to make our history.pushState URLs work like a real site. Remember that the URLs you push should be live URLs that the user can bookmark and share as real entry points to your site.

    Browser support

    Up-to-date copies of Chrome (5+), Safari (5.0+), Firefox (4.0+), and Opera (11.50+) have support for the new History API. Even some mobile browsers work just fine, like Mobile Safari on iOS 4+. Unfortunately, IE 9 and below lack support, but it when it arrives.

    Safari 5.0 sometimes exhibits one oddity: navigating between states causes the loading spinner to appear and stay even when the state has been loaded. This stops when you navigate away using a link or action that does not involve a state saved by the History API.

    Polyfill

    A polyfill does exist for the History API. The aptly named History.js uses HTML4’s hashchange event with document fragment identifiers to mimic the history API in older browsers. If one of the hash URLs is used by a modern browser, it uses replaceState to quietly correct the URL.

    It sounds like magic, but make sure you’re aware of the consequences of using fragment identifiers, as mentioned previously in this article. As such, the author of History.js has put together a guide titled Intelligent State Handling .

    Closing thoughts

    URLs go beyond just the browsing session of a user. They’re historically important markers for resources that could very well remain in use for many years to come. Before you embark on developing your site’s JavaScript, you should give thought to the design of your URLs . Make them meaningful and organised. Make sure you can directly access them without JavaScript. Only then should you add your JavaScript to enhance the browsing experience.

    • Category
    26 Responses on the article “Pushing and Popping with the History API”

    I literally *just* discovered this and worked on this for a client two weeks ago. I even contemplated writing a blog about it because of how cool I found this. This really can be a game changer.

    I’m surprised you did not cover the history.state feature? I believe this is the current spec, though it only works in Firefox. The code used here, while it does work, I thought was part of the original version of the spec. Also, worth mentioning is that you are *supposed* to pass in JavaScript object like in your example. Microsoft’s IE blog has examples where a string is passed in. I’ve not tested support on this across browsers, but it’s worth mentioning.

    I literally cannot tell you how many hours the whole “Store the initial content so we can revisit it later” issue turned out to be an issue for me. It seems unintuitive to me that the default state that you come to is not in the history stack.

    I’m trying the demo page on Internet Explorer 9 and I can’t tell any difference to Chrome so it seems to work.

    I’ll be using this article for a future project, for sure. Thank you!

    thank you for sharing your experience, very informative.

    Could you please elaborate on what you did on the server to handle the fake URLs? Are they manual redirects you inserted, or automatic rewrites? How are they converted?

    Thank you so much!

    In the mean time, I advanced a bit on that same subject. If your web server is Apache, you can configure it to use mod_rewrite, which allows to convert user (or crawler) typed URLs into other server side URLs:

    This is done by adding a number of rules in the .htaccess at the root of your website.

    Literally, just released my new site which makes use of this technique. It works beautifully!

    It’s nice to (maybe) be able to do away with hashes and hashbangs on future ajax-based sites.

    I’m really looking forward to seeing how this gets used as it’s adopted more widely.

    The only issues arise (as is often the case) with cross-browser implementations of this. The history API may be supported by the browsers you mentioned but with varying degrees of success.

    Check out History.js (note the capital ‘H’) by Ben Lupton here:https://github.com/balupton/History.js/ which offers a means to use HTML5 history states while accounting for craoos browser inconsistencies.

    Again, great article. Really gets down to the underlying principles.

    This is great, but if it doesn’t work on IE9 or below it’s pretty useless for production sites currently (without lots of messy hacks and fallbacks!)

    @Raphael + Eric:

    My apologises for the late reply! For the purposes of the demo, I used a very basic mod_rewrite and PHP setup.

    RewriteRule ^(fluffy|socks|whiskers|bob)$ index.php?p=$1

    The above rewrite rule masks the demo URLs and passes it into a single PHP file with a query string of what cat the user is attempting to view. The PHP file then checks $_GET["p"] is valid (always sanitise and check your input as appropriate), then using the pre-defined variables I have set for that cat.

    If this were a real application, the requested path could be used to construct database queries (sanitised of course) to pull in more dynamic content. CMS like WordPress use something like this for their “clean URL” implementation.

    Hope this helps.

    Would it be possible to see that htaccess file closer. I understand everything going on here, within the history api part, except the mod_rewrite. Do you actually have more pages living somewhere for each of these different cats? Refreshing the page after you click a few of the cats returns a page not found error. This has to do with the htaccess part that I am missing, right? Thanks.

    Despite my comment above about poor browser support I decided to switch from hashed URLs (/#/slug) to HTML5 history for http://goproheroes.com and decided to just fall back to normal page loading for IE9 and below using:

    function supports_history_api() {
    return !!(window.history && history.pushState);
    }

    Thanks Mike, your demo helped me out. I had my attempt a bit more complicated than it needed to be.

    However, I’m trying to track and detect when the user steps forward and back through the history so I can add appropriate transitions to my page. Unfortunately, the history API doesn’t let you know if a forward or back button was clicked on a popstate event. That would be really helpful.

    I’m thinking that a cookie might be the only way to attempt to track the order. Saving a previous variable in the state object complicated things and caused browser issues.

    Adding to the list,
    thanks Mike!

    Those adorable kitten’s helped me setup some pushing and popping functions on the soon-to-be released new version of my site!

    Thanks for the tut!

    In your clickhandler function you have something wrong in your post code (it’s ok on kittieland though):

    1. The event object is declared on the arguments as “e”, not “event”.

    2. There is no updateContent(); anywhere! I had to check your working source to see if I was doing something wrong.

    But wait, there’s more! …some updates too:

    3. Chrome 19+ starts to mingle with the state property of the event and stuff.

    As a tip: I used a lame setTimeout() to bind the event listener in order to skip Chrome’s eagerness to pop its state.



    Рекомендуем почитать

    Наверх