JavaScript: salvare una classifica locale e la partita corrente con LocalStorage e SessionStorage

by Anastasia P.
SHARE
JavaScript: salvare una classifica locale e la partita corrente con LocalStorage e SessionStorage
© Guida-HTML5.it

Introduzione

Quando sviluppiamo un piccolo gioco web, possiamo avere due tipi di dati con esigenze differenti:

  • i dati che devono rimanere disponibili anche dopo la chiusura del browser, come una classifica locale;
  • i dati validi soltanto durante la sessione corrente, come il punteggio della partita in corso.

LocalStorage è adatto per conservare informazioni persistenti sul dispositivo dell’utente. I dati rimangono disponibili anche dopo la chiusura della scheda o del browser, finché l’utente non li elimina oppure l’applicazione non li rimuove.

SessionStorage, invece, conserva i dati soltanto nella sessione della scheda corrente. Se l’utente chiude la scheda, i dati vengono eliminati. È quindi utile per informazioni temporanee, come lo stato della partita attualmente aperta.

In questo esempio realizzeremo una piccola applicazione chiamata Classifica Lampo. L’utente potrà inserire il proprio nome, aggiungere punti alla partita corrente e salvare il risultato nella classifica. La classifica sarà memorizzata in LocalStorage, mentre il nome del giocatore e il punteggio temporaneo saranno gestiti con SessionStorage.

Codice completo

<section>
  <h2>Classifica Lampo</h2>

  <label for="playerName">Nome giocatore</label>
  <input id="playerName" type="text" maxlength="20" placeholder="Inserisci il tuo nome">

  <p>
    Punteggio corrente:
    <strong id="currentScore">0</strong>
  </p>

  <button id="addPoints">Aggiungi 10 punti</button>
  <button id="saveResult">Salva risultato</button>
  <button id="clearBoard">Svuota classifica</button>

  <h3>Classifica locale</h3>
  <ol id="leaderboard"></ol>
  <p id="message" aria-live="polite"></p>
</section>

<script>
  // Chiavi centralizzate: evitano errori causati da stringhe duplicate.
  const STORAGE_KEYS = {
    leaderboard: "classifica-lampo",
    playerName: "partita.nome",
    currentScore: "partita.punteggio"
  };

  const playerNameInput = document.querySelector("#playerName");
  const currentScoreElement = document.querySelector("#currentScore");
  const leaderboardElement = document.querySelector("#leaderboard");
  const messageElement = document.querySelector("#message");

  let currentScore = readSessionScore();
  let leaderboard = readLeaderboard();

  // Recupera il nome dalla sessione corrente.
  const savedName = sessionStorage.getItem(STORAGE_KEYS.playerName);

  if (savedName) {
    playerNameInput.value = savedName;
  }

  renderScore();
  renderLeaderboard();

  playerNameInput.addEventListener("input", () => {
    const name = playerNameInput.value.trim();

    // Salvare una stringa vuota non è utile: la rimuoviamo.
    if (name === "") {
      sessionStorage.removeItem(STORAGE_KEYS.playerName);
      return;
    }

    sessionStorage.setItem(STORAGE_KEYS.playerName, name);
  });

  document.querySelector("#addPoints").addEventListener("click", () => {
    currentScore += 10;

    // Il punteggio provvisorio appartiene alla sessione corrente.
    sessionStorage.setItem(
      STORAGE_KEYS.currentScore,
      String(currentScore)
    );

    renderScore();
    showMessage("Sono stati aggiunti 10 punti.");
  });

  document.querySelector("#saveResult").addEventListener("click", () => {
    const name = playerNameInput.value.trim();

    if (name === "") {
      showMessage("Inserisci un nome prima di salvare il risultato.");
      playerNameInput.focus();
      return;
    }

    leaderboard.push({
      name,
      score: currentScore,
      savedAt: new Date().toISOString()
    });

    // Manteniamo soltanto i 10 risultati migliori.
    leaderboard.sort((first, second) => second.score - first.score);
    leaderboard = leaderboard.slice(0, 10);

    localStorage.setItem(
      STORAGE_KEYS.leaderboard,
      JSON.stringify(leaderboard)
    );

    // Dopo il salvataggio, prepariamo una nuova partita.
    currentScore = 0;
    sessionStorage.setItem(STORAGE_KEYS.currentScore, "0");

    renderScore();
    renderLeaderboard();
    showMessage("Risultato salvato nella classifica.");
  });

  document.querySelector("#clearBoard").addEventListener("click", () => {
    const confirmed = window.confirm(
      "Vuoi eliminare tutta la classifica salvata?"
    );

    if (!confirmed) {
      return;
    }

    leaderboard = [];
    localStorage.removeItem(STORAGE_KEYS.leaderboard);
    renderLeaderboard();
    showMessage("Classifica eliminata.");
  });

  function readLeaderboard() {
    const rawData = localStorage.getItem(STORAGE_KEYS.leaderboard);

    if (!rawData) {
      return [];
    }

    try {
      const parsedData = JSON.parse(rawData);

      // Verifichiamo che il valore sia davvero un array.
      return Array.isArray(parsedData) ? parsedData : [];
    } catch (error) {
      console.error("Classifica non valida:", error);
      return [];
    }
  }

  function readSessionScore() {
    const rawScore = sessionStorage.getItem(STORAGE_KEYS.currentScore);
    const parsedScore = Number(rawScore);

    // Evitiamo di usare NaN o valori negativi.
    if (!Number.isFinite(parsedScore) || parsedScore < 0) {
      return 0;
    }

    return parsedScore;
  }

  function renderScore() {
    currentScoreElement.textContent = currentScore;
  }

  function renderLeaderboard() {
    // textContent evita di interpretare il nome come HTML.
    leaderboardElement.innerHTML = "";

    if (leaderboard.length === 0) {
      const emptyItem = document.createElement("li");
      emptyItem.textContent = "Nessun risultato disponibile.";
      leaderboardElement.appendChild(emptyItem);
      return;
    }

    leaderboard.forEach((entry) => {
      const item = document.createElement("li");
      item.textContent = `${entry.name}: ${entry.score} punti`;
      leaderboardElement.appendChild(item);
    });
  }

  function showMessage(message) {
    messageElement.textContent = message;
  }
</script>

Spiegazione

Memorizzare valori semplici

Entrambe le API utilizzano coppie chiave-valore. I valori vengono salvati come stringhe:

localStorage.setItem("tema", "scuro");
const tema = localStorage.getItem("tema");

sessionStorage.setItem("punteggio", "120");
const punteggio = sessionStorage.getItem("punteggio");

Per questo motivo il punteggio deve essere convertito da stringa a numero con Number(). Se si salva un array o un oggetto, è necessario usare JSON.stringify() durante il salvataggio e JSON.parse() durante la lettura.

Perché usare LocalStorage per la classifica?

La classifica rappresenta uno storico che l’utente probabilmente vuole ritrovare nelle visite successive. Per questo viene serializzata in JSON e salvata con:

localStorage.setItem(
  "classifica-lampo",
  JSON.stringify(leaderboard)
);

Quando l’applicazione viene caricata, il contenuto viene recuperato e trasformato nuovamente in un array. Il blocco try...catch è importante: dati modificati manualmente o corrotti non devono bloccare l’intera applicazione.

Perché usare SessionStorage per la partita?

Il nome del giocatore e il punteggio corrente non fanno ancora parte della classifica. Sono dati provvisori e legati alla scheda aperta. SessionStorage permette di recuperarli dopo un semplice ricaricamento della pagina, ma li elimina quando la scheda viene chiusa.

In questo modo distinguiamo chiaramente:

  • LocalStorage: dati persistenti, come i risultati già salvati;
  • SessionStorage: dati temporanei della partita in corso.

Best practice

  • Usa nomi di chiave specifici, ad esempio partita.punteggio, per ridurre il rischio di conflitti con altre applicazioni.
  • Centralizza le chiavi in un unico oggetto, così puoi modificarle facilmente.
  • Non salvare password, token di autenticazione o dati realmente sensibili: Web Storage è accessibile dal codice JavaScript della pagina.
  • Controlla sempre i dati letti. Il contenuto dello storage può essere modificato dall’utente o diventare non valido.
  • Limita la quantità di dati memorizzati. LocalStorage e SessionStorage non sono database e hanno una capacità ridotta.
  • Usa textContent per visualizzare dati inseriti dall’utente, evitando di interpretarli come HTML.
  • Gestisci gli errori di serializzazione e deserializzazione con try...catch.
  • Per applicazioni più grandi, valuta IndexedDB o un database remoto, soprattutto quando servono molti dati o ricerche complesse.

Riepilogo

LocalStorage e SessionStorage offrono un modo semplice per conservare dati direttamente nel browser. La differenza principale riguarda la durata: LocalStorage sopravvive alla chiusura del browser, mentre SessionStorage è legato alla sessione della scheda.

Nell’esempio abbiamo applicato questa distinzione a una classifica di gioco. I risultati completati vengono salvati stabilmente con LocalStorage, mentre il nome e il punteggio della partita corrente vengono mantenuti con SessionStorage. Abbiamo inoltre usato JSON per gestire array, controlli sui dati e funzioni separate per lettura e visualizzazione.

Approfondisci con risorse ufficiali

SHARE