История браузера. History API

Последнее обновление: 02.11.2023

При навигации между страницами браузер сохраняет всю историю о переходах в специальном стеке, который называется history stack. И каждый раз, когда браузер загружает новую веб-страницу или переходит по ссылке на веб-странице, браузер по умолчанию создает новую запись в истории просмотров. В коде JavaScript историю можно получить через свойство history объекта window. Данное свойство представляет тип History.

Объект History для взаимодействия с историей просмотров предоставляет ряд методов и свойств:

  • Свойство length возвращает количество записей в истории просмотров

    console.log("В истории ", history.length, " записей");
  • Свойство state возвращает текущую запись из истории просмотров. По умолчанию при загрузке первой страницы в браузере это свойство равно null

    console.log(history.state);
  • Метод back() переходит к прошлой записи в истории просмотров, аналогично нажатию на кнопку Назад/Back в браузере

    history.back(); // перемещение назад к прошлой странице
  • Метод forward() переходит к следующей просмотренной странице, аналогично нажатию на кнопку Вперед/Next в браузере

    history.forward(); // перемещение вперед к следующей странице
  • Метод go() позволяет перемещаться вперед и назад по истории на определенное число страниц. Методу передается приращение, начиная с текущей веб-страницы. Например, значение -1 приводит к открытию предыдущей веб-страницы, а значение 1 вызывает открытие следующей веб-страницы. Если передается значение, для которого в истории нет соответствующей веб-страницы, этот метод ничего не делает. Если же метод вызывается без значения или со значением 0, текущая веб-страница перезагружается

    history.go(-2);     // переход на 2 страницы назад
    history.go(2);      // переход на 2 страницы вперед 
    history.go(0);      // перезагружаем текущую страницу
    
  • Метод pushState() программно добавляет новую запись в историю просмотров. Он принимает три параметра:

    history.pushState(state, title[, url])
    • Параметр state представляет добавляемый объект в историю просмотров. В качестве такого объекта состояния может быть чем угодно

    • Параметр title устанавливает заголовок. Стоит отметить, что браузеры могут игнорировать этот параметр

    • Параметр url представляет URL-адрес новой записи в истории. Является необязательным. Однако если используется, этот адрес url в этом параметре должен относиться к тому же домену, что и текущая страница. Браузер может устанавливать этот адрес в качестве текущего.

    Простейший пример

    const state = { url: "/", title: "Home", decription: "Home Page" };
    // history.pushState(state, state.title);           // без url
    history.pushState(state, state.title, state.url);    // с url
    console.log(state);  // {url: "/", title: "Home", decription: "Home Page"}
    
  • Метод replaceState() программно заменяет текущую запись в истории просмотров на новую. Он принимает те же три параметра:

    history.replaceState(state, title, [url])

    Простейший пример

    const state = { url: "home", title: "Home", decription: "Home Page" };
    history.replaceState(state, state.title, state.url);
    

Событие popstate

Каждый раз, когда текущая запись в истории посещений меняется (например, при нажатии на кнопку "Назад" в браузере), срабатывает событие popstate. Соответственно если мы хотим обрабатывать перемещение по истории просмотров с помощью кнопок браузера Назад/Вперед, то нам надо обрабатывать данное событие.

Для обработки события popstate в обработчик события передается объект события типа PopStateEvent. В этом объекте свойство state указывает на запись, удаленную из истории просмотров:

window.addEventListener("popstate", (event) => {  
    console.log(event.state);       // получаем старое состояние
});

Перемещение по одностраничному сайту

В качестве примера применения History API определим простейший одностраничный сайт в виде следующей веб-страницы index.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>METANIT.COM</title>
</head>
<body>
<nav><a href="#home">Home</a> | <a href="#about">About</a> | <a href="#contacts">Contacts</a></nav>
<h1 id="content"></h1>
<script>
// Контейнер, в который загружаем контент 
const contentElement = document.getElementById("content");  
// Объект, который содержит содержимое для различных страниц
const pages = { 
    home: { content: "Home Page", url: "#home"},      
    about: { content: "About Page", url: "#about"  },
    contacts: { content: "Contact Page", url: "#contacts"}   
};  
// Обработчик нажатия на ссылки
function handleClick(event){
    // получаем адрес перехода
    const url = event.target.getAttribute("href");
    // получаем имя страницы, которая совпадает с адресом перехода
    const pageName = url.split("#").pop();
    // получаем страницу из объекта pages
    const page = pages[pageName];
    // если текущий адрес совпадает с запрошенным, то игнорируем переход
    if(history.state.url != url) {
        contentElement.textContent = page.content;   
        // добавляем в историю
        history.pushState(page,  // объект state      
            event.target.textContent,   // Title      
            event.target.href           // URL    
        );
        document.title = event.target.textContent; // если браузер не устанавливает заголовок
    }
    return event.preventDefault();  
}  
// устанавливаем обработчик для извлечения состояния в History API
window.addEventListener("popstate", (event) => { 
    if(event.state)       // если  есть состояние 
        contentElement.textContent = event.state.content;   // получаем старое состояние
});
// устанавливаем обработчик нажатия для кнопок
const links = document.getElementsByTagName("a"); 
for (let i = 0; i < links.length; i++) {
    links[i].addEventListener("click", handleClick, true);  
} 
// по умолчанию загружаем Home Page
contentElement.textContent = pages.home.content;  
history.pushState(pages.home, "Home", pages.home.url); 
</script>
</body>
</html>

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

<nav><a href="#home">Home</a> | <a href="#about">About</a> | <a href="#contacts">Contacts</a></nav>

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

<h1 id="content"></h1>

В коде JavaScript мы будем ссылаться на этот элемент через константу contentElement

В коде JavaScript определяем код условных страниц в виде объекта pages:

const pages = { 
    home: { content: "Home Page", url: "#home"},      
    about: { content: "About Page", url: "#about"  },
    contacts: { content: "Contact Page", url: "#contacts"}   
};  

Каждый объект однотипен: содержит свойство content, которое представляет содержимое условной страницы, и свойство url - адрес страницы. Собственно состояние history.state будет представлять один из этих объектов. Но тут важная условность - для простоты названия этих страниц - home/about/constact совпадают с адресами ссылок. Можно было бы отвязать названия, но это привело бы к увеличению логики в сугубо демонстрационном примере.

Для обработки нажатия ссылок определяется функция handleClick, в которую передается объект события. И из этого объекта события через event.target мы можем получить нажатую ссылку и ее данные. Так, в начале получаем адрес ссылки и название страницы (которое равно адресу без начального слеша):

// получаем адрес перехода
const url = event.target.getAttribute("href");
// получаем имя страницы, которая совпадает с адресом перехода
const pageName = url.split("#").pop();
// получаем страницу из объекта pages
const page = pages[pageName];

Получив нужную страницу, смотрим, какая ссылка нажата. Например, мы не хотим, чтобы находясь на определенной странице, пользователь заново загружал данные этой страницы, повторно нажимая на одну и ту же ссылку. И для этой цели берем в истории просмотров текущее состояние и проверяем его свойство url. Если текущее состояние (по сути текущая страница) имеет тот же адрес url, который запрошен, то нет смысла заново перезагружать содержимое страницы:

if(history.state.url != url) {

Если запрошен адрес, отличный от текущего, то устанавливаем в качестве заголовка содержимое (свойство content) текущей страницы и добавляем запись в историю просмотров:

contentElement.textContent = page.content;   
// добавляем в историю
history.pushState(page,  // объект state      
    event.target.textContent,   // Title      
    event.target.href           // URL    
);
document.title = event.target.textContent; // если браузер не устанавливает заголовок

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

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

Для обработки переходов с помощью кнопок браузера Назад/Вперед устанавливаем обработчик для события popstate:

window.addEventListener("popstate", (event) => { 
    if(event.state)       // если  есть состояние 
        contentElement.textContent = event.state.content;   // получаем старое состояние
});

Здесь получаем извлеченное состояние из истории просмотров (event.state) и с помощью его свойства content устанавливаем содержимое заголовка.

В конце устанавливаем обработчик нажатия для кнопок:

const links = document.getElementsByTagName("a"); 
for (let i = 0; i < links.length; i++) {
    links[i].addEventListener("click", handleClick, true);  
} 

И по умолчанию устанавливаем в качестве текущей условной страницы объект home из объекта pages, добавляя при этом соответствующую запись в историю просмотров:

contentElement.textContent = pages.home.content;  
history.pushState(pages.home, "Home", pages.home.url); 

Кинем веб-страницу в браузер и мы сможем переходить по ссылкам как по отдельным страницам:

Хранение состояния страниц в History API в JavaScript

Также вместо символов хеша # для опредения ссылки (то есть индентификаторов фрагмента) также можно использовать слеши /, что, к примеру, будет лучше для индексации страницы поисковиками. Так, пример выше мы можем переписать следующим образом:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>METANIT.COM</title>
</head>
<body>
<nav><a href="/home">Home</a> | <a href="/about">About</a> | <a href="/contacts">Contacts</a></nav>
<h1 id="content"></h1>
<script>
// Контейнер, в который загружаем контент 
const contentElement = document.getElementById("content");  
// Объект, который содержит содержимое для различных страниц
const pages = { 
    home: { content: "Home Page", url: "/home"},      
    about: { content: "About Page", url: "/about"  },
    contacts: { content: "Contact Page", url: "/contacts"}   
};  
// Обработчик нажатия на ссылки
function handleClick(event){
    // получаем адрес перехода
    const url = event.target.getAttribute("href");
    // получаем имя страницы, которая совпадает с адресом перехода
    const pageName = url.split("/").pop();
    // получаем страницу из объекта pages
    const page = pages[pageName];
    // если текущий адрес совпадает с запрошенным, то игнорируем переход
    if(history.state.url != url) {
        contentElement.textContent = page.content;   
        // добавляем в историю
        history.pushState(page,  // объект state      
            event.target.textContent,   // Title      
            event.target.href           // URL    
        );
        document.title = event.target.textContent; // если браузер не устанавливает заголовок
    }
    return event.preventDefault();  
}  
// устанавливаем обработчик для извлечения состояния в History API
window.addEventListener("popstate", (event) => { 
    if(event.state)       // если  есть состояние 
        contentElement.textContent = event.state.content;   // получаем старое состояние
});
// устанавливаем обработчик нажатия для кнопок
const links = document.getElementsByTagName("a"); 
for (let i = 0; i < links.length; i++) {
    links[i].addEventListener("click", handleClick, true);  
} 
// по умолчанию загружаем Home Page
contentElement.textContent = pages.home.content;  
history.pushState(pages.home, "Home", pages.home.url); 
</script>
</body>
</html>

Но в этом случае страница должна располагаться на веб-сервере:

History API в JavaScript
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850