History API JavaScript

La History API è un’interfaccia JavaScript che permette di manipolare la cronologia del browser e gestire la navigazione senza ricaricare la pagina. È fondamentale per creare Single Page Applications (SPA) moderne che offrono un’esperienza di navigazione fluida e veloce.
Come Funziona la History API
La History API lavora con lo stack della cronologia del browser, permettendo di aggiungere, modificare e navigare tra le voci della cronologia. A differenza dei tradizionali cambi di pagina che ricaricano completamente il contenuto, la History API permette di cambiare l’URL e gestire la navigazione mantenendo la pagina caricata.
// Accesso all'oggetto history
console.log(window.history.length); // Numero di voci nella cronologia
console.log(window.history.state); // Stato corrente
L’oggetto history è sempre disponibile tramite window.history e fornisce metodi per manipolare la cronologia e proprietà per ispezionare lo stato corrente.
Navigazione Base
Metodi di Navigazione
I metodi più semplici simulano i pulsanti avanti e indietro del browser:
// Va alla pagina precedente (come premere il pulsante "Indietro")
history.back();
// Va alla pagina successiva (come premere il pulsante "Avanti")
history.forward();
// Va a una posizione specifica nella cronologia
history.go(-2); // Va indietro di 2 pagine
history.go(1); // Va avanti di 1 pagina
history.go(0); // Ricarica la pagina corrente
Questi metodi sono utili per creare controlli di navigazione personalizzati o per implementare logiche specifiche di navigazione.
Manipolazione della Cronologia
pushState()
Il metodo pushState() aggiunge una nuova voce alla cronologia senza ricaricare la pagina:
// Sintassi: pushState(state, title, url)
history.pushState(
{ pagina: "profilo", utente: "mario" }, // Oggetto stato
"Profilo Utente", // Titolo (spesso ignorato dai browser)
"/profilo/mario" // Nuovo URL
);
console.log(window.location.pathname); // '/profilo/mario'
console.log(history.state); // { pagina: 'profilo', utente: 'mario' }
Dopo pushState(), l’URL del browser cambia e viene aggiunta una nuova voce alla cronologia, ma la pagina non viene ricaricata. L’oggetto stato può contenere qualsiasi dato serializzabile che ti aiuta a ricostruire lo stato della pagina.
replaceState()
Il metodo replaceState() modifica la voce corrente della cronologia invece di aggiungerne una nuova:
// Modifica l'URL e lo stato corrente senza aggiungere nuova voce
history.replaceState(
{ pagina: "profilo", utente: "mario", modificato: true },
"Profilo Utente - Modificato",
"/profilo/mario?tab=impostazioni"
);
Questo è utile quando vuoi aggiornare l’URL per riflettere cambiamenti nella pagina (come tab attive o filtri) senza creare nuove voci di cronologia che l’utente dovrebbe navigare.
Event Listener popstate
L’evento popstate si attiva quando l’utente naviga nella cronologia usando i pulsanti del browser:
window.addEventListener("popstate", function (event) {
console.log("Navigazione cronologia rilevata");
console.log("Stato:", event.state);
console.log("URL:", window.location.pathname);
// Ricostruisci l'interfaccia basandoti sullo stato
if (event.state) {
aggiornaInterfaccia(event.state);
} else {
// Stato null indica la pagina iniziale
caricaPaginaIniziale();
}
});
function aggiornaInterfaccia(stato) {
switch (stato.pagina) {
case "home":
mostraHome();
break;
case "profilo":
mostraProfilo(stato.utente);
break;
case "prodotti":
mostraProdotti(stato.filtri);
break;
}
}
L’evento popstate non si attiva per pushState() o replaceState(), ma solo quando l’utente usa i controlli di navigazione del browser.
Implementazione di un Router Semplice
Ecco un esempio di router base per una Single Page Application:
class SimpleRouter {
constructor() {
this.routes = new Map();
this.init();
}
// Registra una rotta
addRoute(path, handler) {
this.routes.set(path, handler);
}
// Naviga a un percorso
navigateTo(path, state = {}) {
// Aggiorna cronologia
history.pushState(state, "", path);
// Carica il contenuto
this.loadRoute(path, state);
}
// Carica il contenuto per una rotta
loadRoute(path, state) {
const handler = this.routes.get(path);
if (handler) {
handler(state);
} else {
this.show404();
}
}
// Inizializza il router
init() {
// Gestisce navigazione con pulsanti browser
window.addEventListener("popstate", (event) => {
this.loadRoute(window.location.pathname, event.state || {});
});
// Carica rotta iniziale
this.loadRoute(window.location.pathname, {});
}
show404() {
document.getElementById("content").innerHTML =
"<h1>Pagina non trovata</h1>";
}
}
// Utilizzo del router
const router = new SimpleRouter();
router.addRoute("/", () => {
document.getElementById("content").innerHTML = "<h1>Home</h1>";
});
router.addRoute("/prodotti", (state) => {
const categoria = state.categoria || "tutti";
document.getElementById(
"content"
).innerHTML = `<h1>Prodotti - ${categoria}</h1>`;
});
router.addRoute("/contatti", () => {
document.getElementById("content").innerHTML = "<h1>Contatti</h1>";
});
Gestione dei Link
Per intercettare i click sui link e usare la History API invece del comportamento predefinito:
// Intercetta tutti i click sui link interni
document.addEventListener("click", function (event) {
const link = event.target.closest("a");
// Verifica se è un link interno
if (link && link.host === window.location.host) {
event.preventDefault();
const path = link.pathname;
const stato = {
timestamp: Date.now(),
source: "link-click",
};
router.navigateTo(path, stato);
}
});
Esempi Pratici
Gestione Filtri e-commerce
function applicaFiltri(filtri) {
const params = new URLSearchParams();
// Costruisci parametri URL
Object.keys(filtri).forEach((key) => {
if (filtri[key]) {
params.append(key, filtri[key]);
}
});
const nuovoURL = `/prodotti?${params.toString()}`;
// Aggiorna cronologia
history.pushState(
{ pagina: "prodotti", filtri: filtri },
"Prodotti filtrati",
nuovoURL
);
// Aggiorna contenuto
caricaProdottiFiltrati(filtri);
}
// Esempio di utilizzo
applicaFiltri({
categoria: "elettronica",
prezzo_max: "500",
marca: "apple",
});
Tab Navigation
function switchTab(tabId) {
// Aggiorna interfaccia
document.querySelectorAll(".tab").forEach((tab) => {
tab.classList.remove("active");
});
document.getElementById(tabId).classList.add("active");
// Aggiorna URL senza creare nuova voce cronologia
const currentState = history.state || {};
history.replaceState(
{ ...currentState, activeTab: tabId },
document.title,
`${window.location.pathname}?tab=${tabId}`
);
}
Considerazioni e Limitazioni
SEO e Accessibilità: Le pagine gestite solo con History API potrebbero avere problemi con i motori di ricerca. Assicurati che il contenuto sia accessibile anche senza JavaScript.
Gestione Errori: Implementa sempre una gestione degli errori per URL non validi o stati corrotti.
Performance: Evita di salvare oggetti troppo grandi nello stato, poiché vengono serializzati e possono impattare le performance.
Browser Support: La History API è ben supportata nei browser moderni, ma considera polyfill per versioni molto vecchie.
Deep Linking: Assicurati che gli URL generati siano accessibili anche caricando la pagina direttamente (server-side rendering o configurazione server appropriata).
La History API è uno strumento potente per creare esperienze web moderne e fluide, ma richiede un’implementazione attenta per garantire una buona esperienza utente e compatibilità con gli standard web.
