Работа с API истории
API истории позволяет веб-сайту взаимодействовать с историей сеанса браузера: то есть со списком страниц, которые пользователь посетил в данном окне. Когда пользователь переходит на новые страницы, например, щелкнув по ссылкам, эти новые страницы добавляются в историю сеанса. Пользователь также может перемещаться вперед и назад по истории, используя кнопки браузера «Назад» и «Вперед».
Основной интерфейс, определённый в API истории, это интерфейс History, и он определяет два довольно различных набора методов:
-
Методы для перехода на страницу в истории сеанса:
-
Методы для изменения истории сеанса:
В этом руководстве нас будут интересовать только методы второго набора, поскольку они имеют более сложное поведение.
Метод pushState() добавляет новую запись в историю сеанса, а метод replaceState() обновляет запись в истории сеанса для текущей страницы. Оба этих метода принимают параметр state, который может содержать любой сериализуемый объект. Когда браузер переходит к этой записи истории, браузер генерирует событие popstate, которое содержит объект состояния, связанный с этой записью.
Основная цель этих API — поддержка веб-сайтов, таких как одностраничные приложения, которые используют JavaScript-API, такие как fetch(), для обновления страницы новым содержимым вместо загрузки новой страницы целиком.
Одностраничные приложения и история сеанса
Традиционно веб-сайты реализуются как набор страниц. Когда пользователи переходят к различным частям сайта, щелкнув по ссылкам, браузер загружает новую страницу каждый раз.
Хотя это отлично подходит для многих сайтов, это может иметь некоторые недостатки:
- Это может быть неэффективно загружать всю страницу каждый раз, когда нужно обновить только часть страницы.
- Сложно поддерживать состояние приложения при переходе между страницами.
По этим причинам популярным шаблоном для веб-приложений является одностраничное приложение (SPA), в котором сайт состоит из одной страницы, и когда пользователь щелкает по ссылкам, страница:
- Блокирует стандартное поведение загрузки новой страницы
- Загружает новое содержимое для отображения
- Обновляет страницу новым содержимым
Например:
document.addEventListener("click", async (event) => {
const creature = event.target.getAttribute("data-creature");
if (creature) {
// Prevent a new page from loading
event.preventDefault();
try {
// Fetch new content
const response = await fetch(`creatures/${creature}.json`);
const json = await response.json();
// Update the page with the new content
displayContent(json);
} catch (err) {
console.error(err);
}
}
});
В этом обработчике кликов, если ссылка содержит атрибут данных "data-creature", то мы используем значение этого атрибута для загрузки JSON-файла, содержащего новое содержимое страницы.
JSON-файл может выглядеть следующим образом:
{
"description": "Bald eagles are not actually bald.",
"image": {
"src": "images/eagle.jpg",
"alt": "A bald eagle"
},
"name": "Eagle"
}
Наша функция displayContent() обновляет страницу с JSON:
// Update the page with the new content
function displayContent(content) {
document.title = `Creatures: ${content.name}`;
const description = document.querySelector("#description");
description.textContent = content.description;
const photo = document.querySelector("#photo");
photo.setAttribute("src", content.image.src);
photo.setAttribute("alt", content.image.alt);
}
Проблема в том, что это нарушает ожидаемое поведение кнопок «Назад» и «Вперед» браузера.
С точки зрения пользователя, он щелкнул по ссылке, и страница обновилась, поэтому это выглядит как новая страница. Если затем он нажмет кнопку «Назад» браузера, он ожидает перейти к состоянию до того, как он нажал на ссылку.
Но, с точки зрения браузера, последняя ссылка не загрузила новую страницу, поэтому «Назад» переведет браузер на ту страницу, которая была загружена до того, как пользователь открыл SPA.
В сущности, эта проблема решается методами pushState(), replaceState(), и событием popstate. Они позволяют нам синтезировать записи истории и получать уведомления, когда текущая запись истории сеанса изменяется на одну из этих записей (например, потому что пользователь нажал кнопки «Назад» или «Вперед»).
Использование pushState()
Мы можем добавить запись в историю в обработчик кликов выше следующим образом:
document.addEventListener("click", async (event) => {
const creature = event.target.getAttribute("data-creature");
if (creature) {
event.preventDefault();
try {
const response = await fetch(`creatures/${creature}.json`);
const json = await response.json();
displayContent(json);
// Add a new entry to the history.
// This simulates loading a new page.
history.pushState(json, "", creature);
} catch (err) {
console.error(err);
}
}
});
Здесь мы вызываем pushState() с тремя аргументами:
-
json: это содержимое, которое мы только что загрузили. Оно будет храниться вместе с записью истории и позднее включено как свойствоstateаргумента, передаваемого обработчику событияpopstate. -
"": это необходимо для обратной совместимости со старыми сайтами и должно всегда быть пустой строкой. -
creature: это будет использоваться в качестве URL для записи. Оно будет отображаться в адресной строке браузера и будет использоваться как значение заголовкаRefererв любых HTTP-запросах, которые выполняет страница. Обратите внимание, что это должно быть одного происхождения с страницей.
Использование события popstate
Предположим, пользователь:
- Щелкнул по ссылке в нашем SPA, поэтому мы обновили страницу и добавили запись истории A с помощью
pushState() - Щелкнул по другой ссылке в нашем SPA, поэтому мы обновили страницу и добавили запись истории B с помощью
pushState() - Нажал кнопку «Назад»
Теперь новой текущей записью истории является A, поэтому браузер генерирует событие popstate, и аргумент обработчика события содержит JSON, который мы передали в pushState() при обработке перехода к A. Это означает, что мы можем восстановить правильное содержимое с помощью обработчика событий, такого как этот:
// Handle forward/back buttons
window.addEventListener("popstate", (event) => {
// If a state has been provided, we have a "simulated" page
// and we update the current page.
if (event.state) {
// Simulate the loading of the previous page
displayContent(event.state);
}
});
Использование replaceState()
Нам нужно добавить ещё один момент. Когда пользователь загружает SPA, браузер добавляет запись в историю. Поскольку это была фактическая загрузка страницы, запись не имеет состояния, связанного с ней. Итак, предположим, что пользователь:
- Загружает SPA: браузер добавляет запись в историю
- Щелкнул по ссылке внутри SPA: обработчик клика обновляет страницу и добавляет запись в историю с
pushState() - Нажал кнопку «Назад»
Теперь мы хотим вернуться к начальному состоянию SPA, но поскольку это навигация в одном документе, страница не будет перезагружена, а так как запись в истории для начальной страницы не имеет состояния, мы не можем использовать popstate для её восстановления.
Решение здесь — использовать replaceState() для установки объекта состояния для начальной страницы. Например:
// Create state on page load and replace the current history with it
const image = document.querySelector("#photo");
const initialState = {
description: document.querySelector("#description").textContent,
image: {
src: image.getAttribute("src"),
alt: image.getAttribute("alt"),
},
name: "Home",
};
history.replaceState(initialState, "", document.location.href);
При загрузке страницы мы собираем все части страницы, которые нам нужно восстановить, когда пользователь возвращается к начальной точке SPA. Эта структура аналогична JSON, который мы загружаем при обработке других переходов. Мы передаём этот объект initialState в replaceState(), что фактически добавляет объект состояния в текущую запись истории.
Когда пользователь возвращается к нашей начальной точке, событие popstate будет содержать это начальное состояние, и мы можем использовать нашу функцию displayContent() для обновления страницы.
Полный пример
Вы можете найти этот полный пример по адресу https://github.com/mdn/dom-examples/tree/main/history-api, а демо-версию — по адресу https://mdn.github.io/dom-examples/history-api/.
См. также
- API истории
-
historyглобальный объект
© 2005–2024 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/API/History_API/Working_with_the_History_API