HTML5 History API уже сегодня и без ограничений

    Библиотека для работы HTML5 History API


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

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

    Использование.

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

    Приведу небольшой пример:
    <!DOCTYPE html>
    <html>
        <head>
            <script type="text/javascript" src="history.js"></script>
            <script type="text/javascript">
                window.onload = function() {
    
                    // просто функция добавляет DIV с нужным нам текстом
                    function appendText( text ) {
                        var div = document.createElement( "div" );
                        div.innerHTML = text;
                        document.body.appendChild( div );
                    }
    
                    // функция для ссылок обрабатывается при клике на ссылку
                    function handlerAnchors() {
    
                        // заполним хранилище чем нибудь
                        var state = {
                            title: this.getAttribute( "title" ),
                            url: this.getAttribute( "href", 2 ) // двоечка нужна для ИЕ6-7
                        }
    
                        // заносим ссылку в историю
                        history.pushState( state, state.title, state.url );
    
                        // тут можете вызвать подгруздку данных и т.п.
                        // ...
    
                        appendText( '<b>Вы перешли по ссылке:</b> ' +
                                        '<span style="color: green;">' + state.url + '</span>' );
    
                        // не даем выполнить действие по умолчанию
                        return false;
                    }
    
                    // ищем все ссылки
                    var anchors = document.getElementsByTagName( 'a' );
    
                    // вешаем события на все ссылки в нашем документе
                    for( var i = 0; i < anchors.length; i++ ) {
                        anchors[ i ].onclick = handlerAnchors;
                    }
    
                    // вешаем событие на popstate которое срабатывает
                    // при нажатии back/forward в браузере
                    window.onpopstate = function( e ) {
    
                        // просто сообщение
                        appendText( '<b>Вы вернулись на страницу:</b> ' +
                            '<span style="color: green;">' + history.location + '</span>' +
                            '<br/><b>state:</b> <span style="color: green;">' +
                            JSON.stringify( history.state ) + '</span><br/><br/>' );
    
                        // тут можете вызвать подгруздку данных и т.п.
                        // ...
                    }
                }
            </script>
        </head>
        <body>
            <h1>Переходите по ссылкам, а затем жмите в браузере кнопки back/forward</h1>
            <a href="/mylink.html" title="Заголовок связанный с ссылкой My Link">My Link</a>
            <a href="/otherlink.html" title="Заголовок связанный с ссылкой Other Link">Other Link</a>
        </body>
    </html>
    

    Как видите, ничего сложного в использовании этой библиотеки нет, из примера выше вы, наверное, заметили, что разница/отличия использования методов описанных в спецификации по интерфейсу History практически отсутствуют. Имеется лишь одна небольшая разница в том, что получение текущей ссылки при срабатывании события popstate, мы получаем из объекта history.location.

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

    В браузерах HTML5 она выполняет лишь роль исправления багов/ошибок при работе с историей. А ссылки имеют нормальный приятный вид, без использования каких либо hash-fallback.

    В браузерах HTML4, конечно же, используется hash-fallback, ибо другого варианта для таких браузеров, конечно же, нет. Но для разработчика внутри JavaScript-кода это не будет являться каким-то недугом, потому как ссылки будут иметь тот вид, что изначально задуманы. И вам не нужно где либо, прописывать hash-ссылки и/или соблюдать правила для поисковиков, что бы те в свою очередь нормально индексировали сайт. Ведь ссылки будут нормального вида, а значит поисковый робот, спокойно перейдет по нужной ссылке.

    Функционал библиотеки.

    Библиотека имеет небольшие тонкости при работе с ней. Как мы уже заметили, у библиотеки есть собственный объект history.location, он ничем не отличается от известного объекта window.location. То есть одним словом это и есть ссылка на объект window.location за исключением того что в браузерах HTML4 он перехватывает getters/setters у оригинального объекта, делает нужные модификации и возвращает результат.

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

    К примеру, это выглядит так:
        history.location = "http://yandex.ru/"; // произойдет переход на страницу Яндекса.
        history.location.hash = "#newhash";  // просто сменим hash на странице.
        // и так далее по всем свойствам, описанным
        // в спецификации по интерфейсу Location
    

    Следующая тонкость библиотеки это его настройка под ваши нужды. Библиотека не имеет каких-то объектов для настройки, а получает их из параметра GET при подключении библиотеки через HTML-элемент script, то есть считывает ссылку при подключении скрипта к сайту.

    Выгладит это примерно так:
    <script type="text/javascript" src="history.js?type=/&redirect=true&basepath=/pathtosite/"></script>
    

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

    Параметр: type

    Особой роли не играет, он нужен для украшения ссылки в браузерах HTML4. В этом параметре вы можете указать совершенно любую строку/символ, который будет добавлен после знака # (hash), тем самым вы просто украсите ссылку нужным знаком/текстом. К примеру, если я задам этому параметру текст: «HelloWorld/», то ссылки будут иметь эту подстроку. Допустим кликнув по ссылке http://somesite.com/folder/page.html мы получим ссылку вида: http://somesite.com/#HelloWorld/folder/page.html. Вы можете поэкспериментировать с этим параметром. По умолчанию в библиотеке этот параметр имеет подстроку "/" (слеш).

    Параметр: basepath

    Один из важных параметров, именно по этому параметру библиотека формирует ссылку для объекта history.location. Он указывает, где находиться корень сайта, обычно сайты лежат прямо в корне домена, но иногда бывает нужно сайт положить в иную папку от основного домена. Для этого и был добавлен этот параметр, что бы сайты, находящиеся вне корня могли полноценно работать в браузерах HTML4.

    Параметр: redirect

    Этот параметр отвечает за переадресацию сайта при переходе по ссылке, скопированной в браузере HTML4 в браузер HTML5. Она просто перенаправляет пользователя перешедшего по ссылке вида: http://somesite.com/#/folder/page.html на ссылку вида http://somesite.com/folder/page.html и наоборот, если пользователь перешел по ссылке из браузера HTML5 в браузер HTML4. Важно помнить, что этот параметр тесно связан с параметром basepath, так как именно по этому параметру он делает выводы, куда перенаправить пользователя.

    Заключение.

    В заключении хочу добавить, что иногда порой нужно узнать в каком браузере мы находимся, в браузере HTML4 или в браузере HTML5. Для этих целей я добавил свойство emulate в объект window.history указывающее на то происходит эмуляция или нет. Если это свойство имеет значение true, значит, мы находимся в браузере HTML4 и работаем с hash-ссылками, в противном случае иное.

    Так же добавлю, что в браузерах HTML5 библиотека исправляет баги/недоделки интерфейса History и все что с ним связано. К примеру, в Safari добавляет объект state в интерфейс History, в браузерах Safari и Chrome убирает initial state (срабатывание события popstate при первой загрузке документа).

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

    Библиотека работает с совершенно любыми фреймворками, и не использует их для своих целей.

    Библиотека была протестирована в браузерах:
    IE 6+
    FireFox 3+
    Opera 11+
    Opera Mobile 11+
    Chrome 17+
    Safari 5+


    Если у вас есть возможность проверить в других браузерах или версиях и вам не составит труда мне отписаться, то я буду очень вам признателен.

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

    Скачать и посмотреть данную библиотеку вы можете на GitHub: https://github.com/devote/HTML5-History-API

    Так же можете посмотреть работоспособность библиотеки на моем, к сожалению не доделанном сайте: http://history.spb-piksel.ru/. Сам сайт не доделан, но работоспособность библиотеки вы можете на нем посмотреть.

    UPD(12.09.2012): Важно! Мне часто стали писать/сообщать о неработоспособности библиотеки в ИЕ9. Проблема связана с тем, что ИЕ9 капризный браузер и придирается ко всем мелочам. Как выяснилось, многие разработчики что используют скрипты взятые с GitHub сталкиваются с проблемами их неработоспособности, потому что ИЕ9 при подключении скриптов ожидает получить заголовок application/javascript, но GitHub возвращает заголовок text/plain. Поэтому подключать скрипты напрямую с GitHub не рекомендуется. Скачайте скрипт к себе на хост и используйте.
    Метки:
    Поделиться публикацией
    Похожие публикации
    Комментарии 49
    • +4
      Аллилуйя! Лучше и придумать почти нельзя, а где можно, там патчи пришлют.
      • 0
        Спасибо, я постарался учесть все по максимому, но с удовольствием выслушаю любые предложения/улучшения.
        • 0
          А чем вам не угодил проект github.com/balupton/history.js? Вроде как задачи те же, захотелось повелосипедить? :)
          • +7
            Нет, дело не в велосипеде, дело тут в очень простом. Суть данной библиотеки максимально снизить написание лишнего кода. А в будущем ее легко извлечь из проекта, при этом не затрагивая основной код сайта. Например если я буду писать с использованием библиотеки от balupton, и захочу отказаться от старых браузеров, то мне нужно будет переписать основной код сайта что бы вырезать эту библиотеку.

            Еще один недостаток библиотеки представленной вами это громоздкость, весит она в разы больше, а с учетом того что сейчас довольно сильно развивается мобильный интернет, это не особо хорошо для них. Конечно это не огромный аргумент, но все же. Ну и конечно же что бы пользоваться библиотекой от balupton, ее нужно изучить, точнее прочитать документацию по ней. Плюс ко всему та библиотека не работает полноценно с браузером ИЕ7, хотя это тоже не аргумент, но все же маленький но аргумент.
            • +2
              Не аргумент… но рагумент :)
              Отличный инструмент.
              • 0
                History.js от balopton'а использую уже давно. Единственные косяки связаны с urlencode/decode (неверно работает со всякими японскими, китайскими и т.п. языками + избыточно работает с некоторыми системными символами). Чтобы выключить поддержку HTML4-браузеров ничего особенного делать не придется, если вы изначально пишите с умом. Достаточно будет просто загрузить History.js только с поддержкой HTML5.
                • 0
                  Я и не заставляю отказаться от привычных инструментов, конечно же каждый выбирает то что ему по душе, тем и пользуется. Но зачем загружать «History.js только с поддержкой HTML5»? Вот этого я не могу понять, да конечно она будет поправлять косяки браузеров, а если их не сегодня а завтра поправят в браузерах? Конечно я утрирую и дождаться от разработчиков браузеров тяжело чего либо, но все же.

                  Суть в том что в моем случае я просто отключу библиотеку и сайт не упадет а продолжит работать как работал. Единственное что нужно будет сделать это заранее об этом подумать и прописывать где нужно примерно такой код:
                  // .........
                  if ( history.pushState ) {
                      // .........
                      var locationObject = history.location || window.location;
                      // .........
                  }
                  // .........
                  

                  Тем самым отключение моей библиотеки не нарушит работоспособность сайта в HTML5 браузерах, а в браузерах HTML4 просто будет обычный переход по ссылкам. Хотя конечно дело выбора и желаний.
                  • 0
                    Ну да. В случае с Балуптоноским методом, простое отключение библиотеки не поможет, так как у него используется объект History, а не history. Но, как и в вашем случае, можно будет сделать довольно быстро работоспособным код этот :)
                    Да и не понимаю я, зачем нужно использовать history.location. Если есть pushState.
                    • 0
                      Да и не понимаю я, зачем нужно использовать history.location. Если есть pushState.
                      Объект history.location содержит ссылку информирующую нас о том куда мы вернулись по истории, не зависимо от присутствия объекта state. То есть в в браузерах HTML5 для этих целей служит обычный window.location и/или document.location. Это я считаю один из важных параметров в отличии от объекта state, который на мой взгляд ввели избыточно.
                • 0
                  Извините, опечатался: balupton'а, конечно.
                • +7
                  Такое ощущение что есть специальные задроты которые только и ждут чтобы указать на «велосипеды». Если мир состоял только вами то вы никуда дальше от развития одноклеточных амеоб не дошли бы.
                  • +1
                    Я лично сам прошел долгий путь от велосипедирования и пришел к выводу что писать что-то свое целесообразно лишь когда:

                    1) похожего инструментария вообще нету
                    2) есть что-то похожее, но оно совсем не нравится

                    Задротства тут никакого не вижу, главное чтобы цели оправдывали средства.
                    • +1
                      Ну да! зато я могу теперь выбрать что использовать.
                      • 0
                        А если просто не нравится, а не совсем? Где грань?
                        • 0
                          Нету никаких граней, это же и коню понятно. Сами определяете границы. Некоторые пишут велосипеды «джаст фор фан» или ради практики в программировании.

                          Ничего не имею против велосипедов.
                          • 0
                            Я в таких случаях форкаю, и делаю так, чтобы «нравилось».

                            Осязание грани «велосипеда» и «инновации» считаю талантом. Нужно уметь вовремя остановиться.
                • 0
                  Кстати, есть ещё более высокоуровневые framework’и, чтобы удобнее писать код с History pushState. Например, Pages.js: github.com/ai/pages.js
                  • 0
                    Спасибо, я посмотрю на досуге на эту библиотеку, но сомневаюсь что в ней увижу что-то новое. Хотя моя цель была сделать библиотеку без добавления всяких лишних методов, которые нужно изучать.
                    • 0
                      Да, тут разные цели. Pages.js не имеет поддержи для старых браузеров, просто framework для упрощения разработки :).
                      • 0
                        Да вы правы, но при использовании моей библиотеки совместно с библиотекой Pages.js боюсь приведет к плачевному результату лишь по одной причине того что у меня реализован history.location который не будет учитывать Pages.js. Хотя конечно же можно попросить разработчиков той библиотеки добавить поддержку.
                        • 0
                          Тут вообще глобальный вопрос, должны ли мы поддерживать старые браузеры. History pushState разработан так, что сайт будет работать и без поддержки pushState, просто не так круто. А если у пользователей старых браузеров сайт один-в-один с современными браузерами, то какой смысл обновлять браузер ;).
                          • 0
                            Да, риторический вопрос, но с заказчиками не поспоришь. Если требуют, то либо делаем либо теряем заказчика, уж таковы реалии и с этим не поспоришь.
                            • 0
                              Ну для заказчика главное бизнес, чтобы деньги приносил, а не какие технологии используются :). Не думаю, что пользователи особо пострадают, если страница будет грузиться старым способом.

                              Тем более, обычно новые технологии отсутствуют на старых медленных браузерах и старых компьютерах. Так что при попытке заставить их показывать «как все», сайт будет просто больше тормозить.
                              • 0
                                Про тормоза, например, утечка памяти из-за особенностей сборщика мусора у старых IE — при обычной загрузке память будет очищаться при переходе между страницами.
                                • 0
                                  Я посмотрел внимательнее на ваш плагин для jQuery, и вы можете добавить поддержку моей библиотеки изменив всего три строки кода в своем плагине.
                                  Это строки 151, 152
                                          if ( Pages._lastUrl != location.href ) {
                                            Pages.open(location.pathname);
                                          }
                                  

                                  заменить на:
                                          if ( Pages._lastUrl != ( history.location || location ).href ) {
                                            Pages.open( ( history.location || location ).pathname );
                                          }
                                  

                                  И строку 448
                                      Pages._lastUrl = location.href;
                                  

                                  заменить на:
                                      Pages._lastUrl = ( history.location || location ).href;
                                  

                                  И у вас появиться поддержка моей библиотеки, как видите ничего сложного и трудоемкого производить не нужно.
                                • 0
                                  Верно подметили, я с вами совершенно согласен. Но люди разные, кто-то имеет иной взгляд на это все.
                    • +1
                      А это нормально что при несколькоих кликах на 1 и ту же ссылку в историю попадают одинаковые сущности?
                      • +2
                        Да это совершенно нормально, поэтому это нужно отслеживать самостоятельно. Но это не глюк библиотеки, так как все браузеры HTML5 делают именно так и в спецификации противоречий этому я не нашел.
                      • 0
                        Я правильно понимаю, что нужно просто подключить эту либу и мое приложение, к примеру на backbone c pushState роутером, будет работать во всех браузерах без fallback-а на хеши?
                        • 0
                          нет, fallback-а на хеши конечно же будет присутствовать в браузерах HTML4 но для этого не нужно делать лишних телодвижений и писать лишний код. Суть в том если backbone использует при работе History объект window.location то скорее всего эта библиотека не совсем будет выполнять требования backbone.

                          К сожалению браузеры HTML4 не позволяют перезагрузить объект window.location и/или document.location, поэтому пришлось добавить дополнительный объект history.location, который и нужно добавить в тот код который тесно связан с объектом window.location при работе с HTML5 History API. В ином случае вполне возможно что библиотека будет корректно работать, но я не тестировал ее совместно с библиотеками которые осуществляют работу с историей.
                        • 0
                          Я бы допилил в соответствии с рекомендациями гугла, тогда получим индексацию ajax контента искаропки.
                          • 0
                            Этого не нужно, потому как ссылки в тегах имеют обычный вид, то есть никакого хеша в ссылках, обычные всеми привычные ссылки. В примере указанной в статье это хорошо видно, и робот зашедший на страницу, спокойно может перейти по нужно ссылке.
                            • 0
                              Ну я бы правда не забывал про ссылки из HTML4 браузеров, которые используют якорь (знак # (hash)). Ведь пользователи старых браузеров тоже могут распространять ссылки с якорями по интернету. Для таких ссылок в вашей библиотеке можно использовать параметр «type=!», если не лень специально для старых браузеров делать поддержку hashbang (#!) на стороне бэкэнда. Я же верно размышляю?

                              Кстати, меня, если честно, слегка смутило, то что вы не используете git теги для версий библиотеки, а вместо этого создали директорию «old». С тегами было бы проще и вам и пользователям библиотеки.
                              • 0
                                Я же верно размышляю?

                                Да все верно

                                Кстати, меня, если честно, слегка смутило, то что вы не используете git теги для версий библиотеки, а вместо этого создали директорию «old». С тегами было бы проще и вам и пользователям библиотеки.

                                Ну я затруднений пока не вижу в этом, на сегодняшний день, версии меняются редко, так как библиотека во всем корректно работает. Ну или просто никто не присылает баг-репорты.
                          • 0
                            Я когда-то делал что-то подобное, но чисто для history-api (без хеш-навигации)
                            github.com/studentIvan/BatmanHand.JS/blob/master/index.html
                            • 0
                              Кстати, кто знает, есть ли разница между window.onpopstate и window.addEventListener('popstate'...?
                              • 0
                                разницы нет никакой, разница лишь в том что при использовании addEventListener вы можете не ограничивать себя и назначать на тоже событие новые и новые перехватчики. Суть в том что событие popstate нельзя отменить и перехватить на стадии погружения. Поэтому разница тут не велика.
                              • 0
                                Вы делали просто обертку для удобства работы с HTML5 History API, моя же библиотека это не обертка, а расширение для HTML4 браузеров, что бы ваша обертка могла работать и в старых браузерах тоже.
                              • 0
                                Подскажите, что-то нигде не нашел информации о лицензии — под какой лицензией опубликована эта библиотека?
                                • 0
                                  Да, уже нашел:

                                   * Dual licensed under the MIT and GPL licenses:
                                   *   http://www.opensource.org/licenses/mit-license.php
                                   *   http://www.gnu.org/licenses/gpl.html
                                  


                                  Вы бы куда-нибудь это на видное место добавили — и в README, и сюда, и в какой-нибудь файл типа LICENSE в корень репозитария…
                                • 0
                                  Спасибо большое за отличную библиотеку.
                                  Я, с тех пор как прочитал webreflection.blogspot.com/2011/02/btw-getters-setters-for-ie-6-7-and-8.html, так и не смог придумать как же это использовать. А Вам это удалось!

                                  Форкнул и рефакторю. Написал в личку
                                  • +1
                                    Да я тоже очень долго решал как применить то что вы привели по ссылке, хотя по приведенной вами ссылке это сырой способ, просто человек писавший метод objectStatic не полностью осведомлен о реалиях VB, от того его способ имеет кучу ошибок и ограничений, поэтому помимо этого мне пришлось перелопатить еще и MSDN что бы получить более полную картину по скрипту VB
                                    • 0
                                      Кстати насчет статьи про getters/setters я их еще применил в этом проекте https://github.com/devote/jsClasses#readme я как то о нем писал на хабре. Если вам интересно конечно их применение не только в библиотеке history.
                                    • 0
                                      VB — это Visual Basic? Зачем он используется в этом скрипте?
                                      • 0
                                        Для возможности использовать setters/getters в ИЕ<9
                                      • 0
                                        Кстати как бы вы поступали с адресами страниц, при которых HTML-содержимое страниц на самом деле не меняется, а изменения применяются только для мультимедиа элементов на самой странице, т.е. адреса на контент которым управляют скрипты. Примеры: адреса слайдов в swf-презентации, адреса определённого видео-фрагмента. Я бы для таких адресов использовал именно якоря (hash), т. к. нужно учитывать, что HTML будет один, и дубликаты однотипного ресурса для поисковиков не хотелось бы создавать.

                                        Ваша библиотека нормально будет работать при совместном использовании адресов без якорей и с ними в HTML5 и HTML4 браузерах?
                                        • 0
                                          Ваша библиотека нормально будет работать при совместном использовании адресов без якорей и с ними в HTML5 и HTML4 браузерах?

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

                                          url: this.getAttribute( «href», 2 ) // двоечка нужна для ИЕ6-7

                                          а можете объяснить, почему? нигде в документации не нашел второго аргумента. или это опечатка?
                                          • 0
                                            О втором параметре написано тут msdn.microsoft.com/en-us/library/ie/ms536429%28v=vs.85%29.aspx
                                            Но так же вы можете почитать на форумах о проблеме получения ссылки у элемента HTMLAnchor средствами getAttribute, в ИЕ6-7 он возвращает абсолютную ссылку, вместо того что реально написано в атрибуте.

                                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.