Управление историей просмотров

89

История просмотров — это функциональность HTML5, которая расширяет возможности объекта history JavaScript. Звучит просто, но нужно знать, когда и зачем следует использовать эту возможность.

Если вам никогда раньше не приходилось встречаться с объектом history, не стоит волноваться по этому поводу. До сих пор он не мог предложить нам ничего полезного. В действительности, традиционный объект history имеет только одно свойство и три основных метода. Это свойство length — содержит информацию о количестве элементов в списке истории просмотров (т.е. в поддерживаемом браузером списке недавно посещенных веб-страниц). Вот пример использования этого свойства:

alert('У вас сохранено ' + history.length +
       ' страниц, в истории браузера.');

Наиболее полезным методом объекта history является метод back(). Этот метод позволяет переместить пользователя на один шаг назад в истории просмотров:

history.back();

Эффект этого метода равнозначен нажатию пользователем кнопки браузера "Назад". Подобным образом можно использовать метод forward() для перемещения на один шаг вперед или метод go() для перехода вперед или назад на определенное количество шагов.

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

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

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

Если вы пока не видите, как это сделать не переживайте, в следующем разделе мы рассмотрим страницу, идеально подходящую для применения истории просмотров.

Проблемы с URL

В статье про объект XMLHttpRequest мы рассмотрели страницу со слайд-шоу. Щелкая по ссылкам "Пред" и "След", посетитель может загружать и отображать на странице новое содержимое. Но самое примечательное в этом примере то, что каждое изображение загружается автономно, не вызывая перезагрузки всей страницы. Эта возможность достигается благодаря объекту XMLHttpRequest.

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

Проблема с URL при динамической загрузке содержимого

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

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

Эта проблема усугубляется в случае, если слайдов огромное количество, например, поток Flickr может содержать десятки и даже сотни изображений.

Традиционное решение: hashbang URL

Для решения этой проблемы некоторые веб-страницы добавляют дополнительную информацию в конец URL. Один из наиболее применяемых (и наиболее спорных) подходов, называющийся hashbang, добавляет в конец URL символы #!, за которыми следует идентифицирующий текст. Вот пример одного из таких URL:

http://mysite.com/exotic_china.html#!/Slide5

Этот метод работает потому, что браузеры рассматривают все, что идет после символа #, как фрагментную часть URL. Таким образом, в случае показанного в примере URL браузер знает, что запрашивается все та же страница exotic_china.html, но только с дополнительным фрагментом в конце.

С другой стороны, посмотрим, что произойдет, если код JavaScript изменит URL, не подставив символы #!:

http://mysite.com/exotic_china.html/Slide5

Теперь браузер отправит этот запрос веб-серверу и попытается загрузить новую страницу. Но это явно не то, что нам требуется.

Как же реализовываться метод hashbang? Сначала нам нужно изменить URL, который отображается в браузере, когда страница загружает новое изображение. Это можно сделать, присвоив значение свойству location.href с помощью кода JavaScript. Далее, по загрузке страницы нам нужно исследовать URL, извлечь из него фрагментную часть и получить с веб-сервера соответствующее динамическое содержимое.

Хотя метод hashbang получил широкое распространение, он также порождает много споров о его соответствии требованиям. Веб-разработчики начали отказываться от его использования по нескольким причинам:

Хотя веб-мастера по-разному относятся к этому методу, большинство из них разделяет мнение, что такие адреса представляют короткий этап в развитии интернета, и со временем им на смену придет возможность отслеживания истории сеансов.

HTML5-решение: история сеансов

HTML5 предоставляет другое решение проблемы с URL в отслеживании истории сеансов. Можно изменять URL любым образом, не требуя при этом добавления в него странных символов, как в случае с методом hashbang. Например, когда страница exotic_china.html загрузит пятый слайд, ее URL можно изменить так:

http://mysite.com/exotic_china5.html

В этом случае браузер не будет пытаться запрашивать страницу exotic_china5.html, а оставит прежнюю страницу, но загрузит для нее указанный слайд, а это нам и нужно. То же самое происходит, когда посетитель перемещается в обратном порядке в истории просмотра. Например, если посетитель перейдет к следующему слайду (и URL изменится на exotic_china5.html), а потом возвратится назад к четвертому (возвращая URL к exotic_china4.html), браузер сохраняет текущую страницу, и активирует событие, с помощью которого можно загрузить соответствующий слайд и восстановить правильную версию страницы.

Хотя с первого взгляда все выглядит как идеальное решение, в нем есть значительный недостаток. Чтобы эта система работала должным образом, для каждого используемого URL нужно в действительности создать страницу. Для нашего примера это означает, что надо создать страницы exotic_china1.html, exotic_china2.html, exotic_china3.html и т.д. Ведь посетитель может захотеть обратиться к этим страницам напрямую, например, через закладку, введя адрес вручную, щелкнув по ссылке в сообщении электронной почты и т.п.

Для крупных веб-сайтов (например, Facebook или Flickr) это не представляет большой проблемы, т.к. они могут использовать серверный код и предоставить содержимое одного и того же слайда в другой упаковке. Но самостоятельному веб-разработчику для этого может потребоваться приложить несколько больше усилий.

Теперь, когда мы понимаем, каким образом история сеансов связана со страницами, собственно использование ее не представляет никаких трудностей. История сеансов состоит всего лишь из двух методов и одного события, добавленных к объекту history.

Наиболее важным является метод pushState(), который позволяет изменить часть URL, обозначающую страницу, в любое требуемое время. По причинам безопасности остальная часть URL изменениям не поддается. (Возможность изменять основную часть URL предоставила бы мощное средство для хакеров для подделывания веб-сайтов.)

Метод pushState() принимает три аргумента, обязательным из которых является только третий — URL, выводящийся в строке адреса браузера. Первым параметром могут быть любые данные, сохраняемые для представления текущего состояния данной страницы. Как мы увидим далее, эти данные можно использовать, чтобы восстановить состояние страницы, если пользователь возвратится к данному URL посредством списка истории посещенных страниц браузера.

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

Далее приведен код, который нужно добавить в страницу exotic_china.html, чтобы изменять ее URL в соответствии с текущим отображаемым слайдом:

function nextSlide() {
  if (slideNumber == 5) {
    slideNumber = 1;
  } else {
    slideNumber += 1;
  }
  
  // Добавленный код
  history.pushState(slideNumber, null, "exotic_china" 
              + slideNumber + ".html");
  //
  
  goToNewSlide();
  return false;
}

function previousSlide() {
  if (slideNumber == 1) {
    slideNumber = 5;
  } else {
    slideNumber -= 1;
  }
  
  // Добавленный код
  history.pushState(slideNumber, null, "exotic_china" 
              + slideNumber + ".html");
  //

  goToNewSlide();
  return false;
}

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

Результаты исполнения кода показаны на рисунке:

Видоизмененный URL

Используя метод pushState(), также следует иметь в виду событие onPopState, которое является его естественным дополнением. В то время как метод pushState() вставляет новый элемент в список История (History) браузера, событие onPopState предоставляет средство для обработки этого элемента, когда посетитель возвратится к нему.

Чтобы понять работу метода, рассмотрим, что происходит, когда посетитель просматривает все слайды. В процессе просмотра URL в адресной строке браузера меняется с exotic_china.html на exotic_china1.html, потом на exotic_china2.html, на exotic_china3.html и т.д. Хотя страница в действительности не изменяется, все эти URL добавляются в историю просмотра браузера.

Если пользователь щелкнет по ссылке для перехода на предыдущий слайд (например, с exotic_china3.html на exotic_china2.html), активируется событие onPopState. Это событие предоставляет коду информацию состояния, сохраненную ранее посредством метода pushState(). Задача программиста заключается в использовании этой информации, чтобы восстановить требуемую версию страницы. В настоящем примере это означает загрузку соответствующего слайда:

window.onpopstate = function(e) {
  if (e.state != null) {
    // Определяем номер слайда для данного состояния. 
    // (Этот номер также можно было вырезать из URL, используя 
    // свойство location.href, но для этого потребуется больше работы.)
    slideNumber = e.state;
    
    // Запрашиваем этот слайд у веб-сервера
    goToNewSlide();
  }
}

Обратите внимание, что в этом примере выполняется проверка на наличие объекта состояния, прежде чем приступать к работе. Это делается из-за того, что некоторые браузеры (включая Chrome) активируют событие onPopState при начальной загрузке страницы, даже если метод pushState() еще не вызывался.

Существует еще один метод объекта history — replaceState(), но он используется не так часто. Метод replaceState() можно применять для того, чтобы заменить информацию о состоянии, которая связана с текущей страницей, не добавляя при этом ничего в список История (History).

Поддержка браузерами истории сеансов

Функциональность отслеживания истории сеансов является сравнительно новой возможностью, хотя все последние версии основных браузеров поддерживают ее, за исключением Internet Explorer:

Поддержка браузерами истории сеансов
Браузер IE Firefox Chrome Safari Opera Safari iOS Android
Минимальная версия - 4 8 5 11.5 4.2 -

Проблему, возникающую вследствие отсутствия поддержки браузером истории сеансов, можно решить несколькими способами. Если ничего не делать, просто не будут выводиться составные URL. Как раз это и происходит, если загрузить рассмотренный пример в Internet Explorer — какой бы слайд мы не загрузили, URL остается неизменно exotic_china.html.

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

Самый сложный вариант — использовать историю сеансов там, где это возможно, а где невозможно, применять резервное решение в виде URL типа hashbang. (Этот метод используется на Facebook.) Недостатком данного метода является необходимость применять два разных подхода в одной и той же странице. Можно также использовать заплатку JavaScript.

Пройди тесты
Лучший чат для C# программистов