History API JavaScript

Edoardo Midali
Edoardo Midali

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.

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>";
});

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.