JavaScript History API
JavaScript History API позволяет управлять историей браузера без перезагрузки страницы — незаменимый инструмент для одностраничных приложений.
Введение в JavaScript History API
В современной веб-разработке создание бесшовного пользовательского опыта нередко требует управления историей браузера. JavaScript History API позволяет читать и изменять историю сеанса браузера — список страниц (и состояний URL), которые пользователь посетил в текущей вкладке. Используя этот API, разработчики могут обновлять адресную строку и стек «назад/вперёд» без полной перезагрузки страницы, что необходимо одностраничным приложениям (SPA) для быстрой и нативной работы.
На этой странице рассмотрены три ключевых метода (pushState, replaceState и вспомогательные методы навигации), порядок обработки события popstate, типичные ошибки, которые допускают разработчики, а также рекомендации по использованию API в продакшене.
Зачем нужен History API
До появления этого API единственным способом изменить URL была навигация, которая приводила к перезагрузке всего документа. SPA отображают новые «страницы» с помощью JavaScript, поэтому им необходимо, чтобы URL оставался синхронизированным без перезагрузки — иначе кнопка «Назад», закладки и ссылки для совместного использования перестают работать.
History API решает эту задачу, предоставляя возможность:
- Добавлять новую запись в стек «назад/вперёд» (
pushState). - Изменять текущую запись на месте (
replaceState). - Реагировать на перемещение пользователя по стеку с помощью кнопок «Назад/Вперёд» (
popstate).
Данные текущей записи можно прочитать через свойство history.state (только для чтения), а history.length показывает, сколько записей находится в стеке сеанса. Для формирования URL, передаваемых этим методам, удобным вспомогательным инструментом служит объект URL.
Объект history в двух словах
API доступен через глобальный объект window.history (можно писать просто history):
| Член | Что делает |
|---|---|
history.pushState(state, title, url) | Добавляет новую запись в стек истории и обновляет URL. |
history.replaceState(state, title, url) | Заменяет текущую запись — новая запись в стеке не создаётся. |
history.state | Копия object состояния текущей записи (только для чтения). |
history.length | Количество записей в истории сеанса. |
history.back() | Переходит на одну запись назад (аналог кнопки «Назад» в браузере). |
history.forward() | Переходит на одну запись вперёд. |
history.go(n) | Перепрыгивает на n записей (отрицательное значение — назад, положительное — вперёд). |
Использование History API в веб-приложениях
Навигация между состояниями
History API позволяет перемещаться между различными состояниями приложения без перезагрузки страницы. pushState() принимает три аргумента: object state (любое сериализуемое значение), title (большинство браузеров его игнорируют, поэтому передавайте пустую строку) и url (разрешается относительно текущей страницы и должен принадлежать тому же источнику). Вот как добавить новое состояние:
<div>
<button onclick="changeState()">Go to New State</button>
</div>
<script>
// Function to change state
function changeState() {
const newState = { id: 'newState' };
// Push a new state to the history stack
window.history.pushState(newState, 'New State', 'new-state-url');
}
</script>Этот код добавляет новое состояние в стек истории с помощью pushState(). Обратите внимание на то, чего он не делает: он не загружает new-state-url и не генерирует событие popstate. pushState только обновляет адресную строку и стек — отображение соответствующего контента остаётся на вашей совести.
Обработка события Popstate
Когда пользователь нажимает кнопку «Назад» или «Вперёд» в браузере (либо вы вызываете history.back() / history.forward()), генерируется событие popstate. Свойство state события содержит object состояния, переданный ранее в pushState/replaceState для теперь активируемой записи. Обрабатывайте его для восстановления нужного представления:
window.addEventListener('popstate', function(event) {
if(event.state) {
console.log('State changed:', event.state);
// Handle the state object here
}
});Этот обработчик реагирует на события popstate, фиксирует изменения и позволяет корректировать состояние в зависимости от истории навигации пользователя. Если event.state равно null, пользователь вернулся к записи, созданной при обычной загрузке страницы (без object состояния) — в этом случае отображайте представление по умолчанию.
Замена текущего состояния
Иногда нужно обновить текущую запись без добавления новой в стек — например, синхронизировать фильтр или выбранную вкладку с URL, где добавление шага для кнопки «Назад» было бы неудобным. Именно для этого предназначен replaceState():
<div>
<button onclick="replaceCurrentState()">Replace State</button>
<p id="replace-status">Ready</p>
</div>
<script>
function replaceCurrentState() {
const newState = { id: 'replacedState' };
// Replace the current state
window.history.replaceState(newState, 'Replaced State', 'replaced-state-url');
document.getElementById('replace-status').textContent = 'State replaced successfully!';
}
</script>Этот код обновляет текущую запись на месте. Кнопка «Назад» по-прежнему ведёт на страницу, которая была до этого, поскольку replaceState не добавляет новый шаг.
Полный пример в стиле SPA
Теперь соберём всё воедино. Пример ниже имитирует одностраничное приложение: нажатие кнопки меняет содержимое div и обновляет URL с помощью pushState, а обработчик popstate восстанавливает правильный контент при навигации пользователя кнопками «Назад/Вперёд». Это тот же паттерн, который клиентские маршрутизаторы используют под капотом.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>SPA Style History API Example</title>
</head>
<body>
<h1>Page Navigation with History API</h1>
<div id="content">Start Page</div>
<!-- Buttons for navigation -->
<button onclick="loadPage('page1')">Load Page 1</button>
<button onclick="loadPage('page2')">Load Page 2</button>
<button onclick="manualGoBack()">Go Back</button>
<button onclick="manualGoForward()">Go Forward</button>
<!-- Display the current status of the history -->
<p id="historyStatus">History Status: Start</p>
<script>
// Loads a "page" and updates the browser's history state
function loadPage(page) {
const state = { page: page }; // State to be pushed to history
history.pushState(state, `Page ${page}`, `${page}.html`); // Pushing state to the history
document.getElementById('content').innerHTML = `<h2>This is ${page.replace('page', 'Page ')}</h2>`; // Update the content
updateHistoryStatus(state); // Update the history status display
}
// Handles the browser's back and forward button actions
window.addEventListener('popstate', function(event) {
if (event.state) {
// Update the page content and history status when navigating through history
document.getElementById('content').innerHTML = `<h2>This is ${event.state.page.replace('page', 'Page ')}</h2>`;
updateHistoryStatus(event.state);
} else {
// Fallback content when the history does not have any state
document.getElementById('content').innerHTML = `<h2>Start Page</h2>`;
document.getElementById('historyStatus').textContent = "History Status: Start";
}
});
// Updates the display of the current history status
function updateHistoryStatus(state) {
document.getElementById('historyStatus').textContent = `History Status: ${state.page}`;
}
// Function to manually trigger going back in history
function manualGoBack() {
history.back();
}
// Function to manually trigger going forward in history
function manualGoForward() {
history.forward();
}
</script>
</body>
</html>Как это работает:
- Динамическая загрузка страниц —
loadPage()изменяет содержимое div и вызываетpushStateдля добавления записи в историю, благодаря чему каждая «страница» становится полноценной точкой навигации «назад/вперёд». - Восстановление при навигации — обработчик
popstateчитаетevent.state.pageи повторно отображает соответствующий контент; еслиevent.stateравноnull, происходит возврат на стартовую страницу. - Привычный интерфейс — кнопки вызывают
history.back()иhistory.forward(), создавая ощущение многостраничного сайта без единой полной перезагрузки.
Типичные ошибки
Ряд особенностей регулярно ставит разработчиков в тупик:
pushStateне генерируетpopstate. Событие генерируется только при навигации пользователя (кнопки «Назад/Вперёд»,history.go()). После вызоваpushStateотображайте новое представление самостоятельно в той же функции.- Аргумент
titleигнорируется. Практически все браузеры его не учитывают, поэтому передавайте"". Чтобы изменить заголовок вкладки, установитеdocument.titleнапрямую. - URL должны принадлежать тому же источнику. Передача URL другого источника вызывает
SecurityError. Путь разрешается относительно текущего документа. - Состояние должно быть сериализуемым. object состояния клонируется алгоритмом структурированного клонирования, поэтому функции, DOM-узлы и экземпляры классов хранить нельзя — кроме того, существует ограничение по размеру (обычно несколько МБ).
- Перезагрузка страницы запускает приложение по новому URL. Когда пользователь обновляет URL, добавленный через
pushState, сервер должен ответить на этот путь (или SPA должно обработать его при загрузке), иначе пользователь получит ошибку 404. Серверный фолбэк — это задача маршрутизации, которую SPA обязано решить.
Рекомендации по использованию History API
- Храните в состоянии минимум данных. Записывайте в object состояния идентификатор или несколько примитивов, а остальное восстанавливайте из них. Не сохраняйте большие массивы данных в историю — это раздувает сеанс и рискует превысить лимит размера.
- Всегда самостоятельно обновляйте
document.titleпри смене «страницы», поскольку аргументtitleигнорируется. - Управляйте позицией прокрутки. Браузеры восстанавливают прокрутку при
popstateчерезhistory.scrollRestoration; установите значение"manual", если хотите управлять прокруткой самостоятельно. Об API прокрутки читайте в разделе размеры окна и прокрутка. - Предусмотрите серверный фолбэк, чтобы обновление или передача URL, добавленного через
pushState, продолжали работать. - Используйте
replaceStateдля ненавигационных обновлений (фильтры, вкладки), чтобы кнопка «Назад» оставалась осмысленной. - Изучайте историю — используйте
history.stateдля получения текущего object состояния иhistory.lengthдля количества записей в стеке сеанса.
Поскольку History API только изменяет URL, он естественно сочетается с fetch для загрузки данных, нужных каждой «странице».
Заключение
JavaScript History API позволяет управлять историей сеанса браузера — добавлять, заменять записи и реагировать на навигацию — без полной перезагрузки страниц. Освоение pushState, replaceState и события popstate с учётом особенностей, связанных с popstate, сериализацией и URL одного источника, делает одностраничные приложения такими же быстрыми и удобными для навигации, как традиционные многостраничные сайты.