Кратко
Секция статьи "Кратко"History API даёт доступ к управлению историей браузера в рамках текущей сессии. Браузер создаёт новую сессию, когда пользователь открывает новую вкладку или новое окно браузера.
С помощью History API можно переходить по истории вперёд, назад и управлять содержимым истории. Доступ к API осуществляется с помощью объекта window
.
Основные методы:
back
перемещает пользователя по истории на страницу назадforward
перемещает пользователя по истории на страницу вперёдgo
универсальный метод для перемещения по истории вперёд или назадpushState
добавляет новую запись в истории сессииreplaceState
изменяет текущую запись в истории сессии
Как пишется
Секция статьи "Как пишется"Сгруппируем методы и свойства History API на две части:
- используемые для перемещения по истории в текущей сессии.
- используемые для управления историей.
Перемещение по истории браузера
Секция статьи "Перемещение по истории браузера"Метод back
перемещает пользователя по истории назад. Это работает аналогично нажатию кнопки «Назад» в браузере.
Метод back
не принимает аргументов и не возвращает результата.
window.history.back()
window.history.back()
Чтобы переместиться по истории вперёд, используется метод forward
. Он работает аналогично кнопке «Вперёд» в браузере.
Метод forward
не принимает аргументов и не возвращает результата.
window.history.forward()
window.history.forward()
Метод go
является универсальным и позволяет переместиться по истории вперёд или назад относительно текущей страницы.
Метод принимает один аргумент – число, которое определяет, на сколько шагов по истории вперёд или назад нужно перейти. Отрицательное значение определяет сколько шагов назад, а положительные – вперёд.
window.history.go(2)// переместит на две позиции вперёд по историиwindow.history.go(-1)// переместит назад на одну страницу
window.history.go(2) // переместит на две позиции вперёд по истории window.history.go(-1) // переместит назад на одну страницу
Нулевая позиция 0
означает текущую страницу и вызов с нулём обновляет текущую страницу.
window.history.go(0)
window.history.go(0)
Метод back
аналогичен вызову window
, а метод forward
— вызову window
.
Свойство length
хранит количество записей в истории браузера в текущей сессии, включая текущую страницу. То есть новая история всегда будет начинаться с 1.
length
работает только для чтения, при изменении значения ничего не произойдёт.
Управление историей
Секция статьи "Управление историей"Для создания новой записи в истории используется метод pushState
, а для модификации текущей записи – replaceState
.
Оба метода похожи с точки зрения использования и оба принимают три аргумента:
- объект состояния, в который можно добавить любые данные, необходимые для навигации;
- строка для обозначения заголовка новой записи;
- новый URL-адрес, этот параметр опциональный;
У передаваемых аргументов есть ограничения, о которых стоит помнить:
- В первый аргумент-объект можно записать любой объект с любыми данными, главное чтобы объект был сериализуемым. Браузеры могут накладывать ограничения на размер такого объекта.
- Второй аргумент игнорируется всеми браузерами кроме Safari. Из-за этого на практике чаще всего передают пустую строку.
- Третий аргумент, URL-адрес, необязательный. Если этот аргумент не был задан, то будет использован текущий URL. Новый URL-адрес должен использовать тот же протокол, домен и порт, иначе будет выброшена ошибка. Если новая запись ведёт на новый относительный адрес, то можно не передавать адрес полностью вместе с доменом, а записать только относительную часть через слэш. Например,
./ profile
Рассмотрим работу window
на примере. В этом примере у нас получится замкнутый круг переходов по страницам. Вы можете поиграться с примером, ниже я разберу код, который используется в демке.
Предположим, что мы зашли на главную страницу сайта с новой вкладки. С помощью кнопки добавим новую запись в историю:
window.history.pushState( {}, '', 'about.html')
window.history.pushState( {}, '', 'about.html' )
Адрес в браузерной сроке location
изменился, а так же обновилось количество записей в истории history
. Теперь если нажать кнопку перезагрузки страницы, то мы увидим новую страницу.
Теперь можем изменить текущую запись в истории, нажав на кнопку. Ниже приведён фрагмент код, который делает изменение истории:
window.history.replaceState( {}, '', 'index.html')
window.history.replaceState( {}, '', 'index.html' )
Адрес в браузерной строке снова изменился, но количество записей осталось прежним, так как мы ничего не добавили. Далее продолжаем путь на следующую страницу.
Страница цен является последним пунктом в примере, с него можно только вернуться по истории назад на предыдущую страницу.
window.history.back()// Или можно сделать window.history.go(-1)
window.history.back() // Или можно сделать window.history.go(-1)
Помните, как на предыдущей странице видели страницу «О нас» по ссылке about
? Мы же попали на главную страницу. Это произошло потому что мы изменили текущую запись в истории, изменив её URL-адрес. В результате, когда мы возвращаемся назад по истории, то используем изменённый URL.
Как понять
Секция статьи "Как понять"Основным способом в вебе навигации являются ссылки <a>
. С помощью ссылок страницы соединяются друг с другом.
Особенность этого способа навигации в том, что при переходе на новый адрес страница перезагружается. Каждый такой переход сохраняется в истории браузера. История может выглядеть так:
Верхние пункты списка – это недавно посещённые страницы, а последний пункт — страница, с которой началась сессия.
С другой стороны, есть одностраничные приложения, которые работают без перезагрузки страницы. Все содержимое они отрисовывают с помощью JavaScript. Приложение может перерисовать всю страницу, но на истории браузера это никак не отобразится.
История в браузере будет выглядеть как единственный пункт:
Это ведёт к плохому пользовательскому опыту, потому что пользователю сложно понять по каким страницам он перемещался.
Отсутствие истории ломает нативное поведение браузерных кнопок вперёд и назад, что очень важно, так как эти кнопки часто привязаны к жестам или системным кнопкам в смартфонах.
Сначала эту проблему решали добавлением хэша в адрес сайта с помощью поля window
. Присвоив новое значение в location
, адресная строка сразу же обновлялась и в историю добавлялась новая запись. Но с помощью хэша нельзя строить читаемые многоуровневые урлы, например https
.
С появлением History API появилась возможность напрямую добавлять записи в историю просмотров. History API так же расширяет возможности для программного перемещения по истории браузера. Это позволяет создавать полноценную навигацию в одностраничных приложениях, менять адрес в браузерной строке и все это будет происходить без перезагрузки страницы.
На практике
Секция статьи "На практике"🛠 Узнать, когда переход между страницами завершился
Секция статьи "🛠 Узнать, когда переход между страницами завершился"Методы для перемещения по истории браузера back
, forward
и go
являются асинхронными, но они не принимают колбэков и не возвращают Promise.
Чтобы узнать, когда переход был завершён необходимо подписаться на событие popstate
окна:
window.addEventListener('popstate', (event) => { console.log(`Перешли на адрес "${document.location}"`)})
window.addEventListener('popstate', (event) => { console.log(`Перешли на адрес "${document.location}"`) })
После этого можно добавить несколько записей в историю и сделать переходы. Если запустить скрипт на главной Доки — https://doka.guide/.
history.pushState({}, '', '/js/')history.pushState({}, '', '/css/')history.back()// Напечатает: Перешли на адрес "https://doka.guide/js/"history.back()// Напечатает: Перешли на адрес "https://doka.guide/"history.go(2)// Напечатает: Перешли на адрес "https://doka.guide/css/"
history.pushState({}, '', '/js/') history.pushState({}, '', '/css/') history.back() // Напечатает: Перешли на адрес "https://doka.guide/js/" history.back() // Напечатает: Перешли на адрес "https://doka.guide/" history.go(2) // Напечатает: Перешли на адрес "https://doka.guide/css/"
Подписка на событие popstate
используется в большинстве современных роутеров для фреймворков, чтобы давать разработчикам возможность запускать функции при перехода на новую страницу.
В методы pushState
и replaceState
первым аргументом необходимо передавать объект с данными состояния. Если никакие данные нам не нужны, можно передавать пустой объект.
Сами передаваемые данные никак не влияют на URL-адрес или на сам переход переход. Данные будут скопированы в поле state
в объекте события во время срабатывания события popstate
.
window.addEventListener('popstate', (event) => { console.log(`Данные навигации: ${JSON.stringify(event.state)}`)});
window.addEventListener('popstate', (event) => { console.log(`Данные навигации: ${JSON.stringify(event.state)}`) });
Объект с данными может быть полезен чтобы передавать информацию между переходами. Например, интернет-магазин, в котором есть фильтры товаров, может захотеть синхронизировать фильтры с URL-адресом, чтобы каждый пользователь мог поделится ссылкой со всеми предустановленными фильтрами.
В приложения храним такие данные в виде простого объекта, а для URL будем преобразовывать его в строку параметров URL.
Рассмотрим пример базовой синхронизации параметров с урлом. Создадим функцию, которая будет превращать объект в строку URL-параметров. Например, объект { priceFrom
в
:
function transformToURLParams(filters) { const query = Object.entries(filters) .map(([key, value]) => { return `${key}=${value}` }) .join('&') return `?${query}`}
function transformToURLParams(filters) { const query = Object.entries(filters) .map(([key, value]) => { return `${key}=${value}` }) .join('&') return `?${query}` }
Затем добавим функцию, которая будет обновлять историю и устанавливать выбранные фильтры в строку URL в браузере:
function syncURL(filters) { const path = document.location.pathname const query = transformToURLParams(filters) window.history.replaceState(filters, '', `${path}${query}`)}
function syncURL(filters) { const path = document.location.pathname const query = transformToURLParams(filters) window.history.replaceState(filters, '', `${path}${query}`) }
Теперь при каждом изменении фильтров достаточно вызвать функцию syncURL
:
syncURL({ priceFrom: 200, priceTo: 1000, manufacturer: 'Adidas'})
syncURL({ priceFrom: 200, priceTo: 1000, manufacturer: 'Adidas' })
Фильтры могут работать и в обратную сторону — после загрузки страницы мы можем восстанавливать информацию о фильтрах из URL страницы.