JavaScript

Caricamento lazy delle immagini e modali accessibili: due pattern DOM sempre utili

calendar_today personTeam EGSOFT schedule4 min di lettura
info

Disclaimer: le informazioni pubblicate in questa sezione hanno finalità di pura divulgazione tecnica. EG Software S.r.l. non si assume alcuna responsabilità per un utilizzo improprio dei contenuti, né per eventuali danni diretti o indiretti derivanti dalla loro applicazione, e non garantisce l'aggiornamento, l'accuratezza o la completezza delle informazioni riportate. Prima di utilizzare in produzione codice o procedure qui descritte, verificane sempre l'adeguatezza al proprio contesto.

Non tutte le pagine hanno bisogno di un framework per essere veloci e usabili: due piccoli pattern in JavaScript vanilla — il caricamento differito delle immagini e una gestione centralizzata delle finestre modali — coprono buona parte dei casi reali, da una galleria prodotti a un form di contatto in overlay.

Caricare le immagini solo quando servono

Su una pagina con molte immagini (una lista di schede prodotto, una galleria), caricarle tutte subito spreca banda e rallenta il primo rendering. Con IntersectionObserver si può rimandare il caricamento reale a quando l'immagine sta per entrare nel viewport, mantenendo un fallback per i browser molto vecchi che non lo supportano:

document.addEventListener('DOMContentLoaded', () => {
  const immagini = document.querySelectorAll('img.lazy');

  // Fallback: senza IntersectionObserver si caricano tutte subito
  if (!('IntersectionObserver' in window)) {
    immagini.forEach((img) => {
      if (img.dataset.src) img.src = img.dataset.src;
    });
    return;
  }

  const osservatore = new IntersectionObserver((voci) => {
    voci.forEach((voce) => {
      if (!voce.isIntersecting) return;

      const img = voce.target;
      if (img.dataset.src) {
        img.src = img.dataset.src;
        img.addEventListener('load', () => img.classList.add('caricata'), { once: true });
        img.addEventListener('error', () => {
          img.src = img.dataset.placeholder || '/images/placeholder.png';
        }, { once: true });
      }
      osservatore.unobserve(img); // una volta caricata, non serve più osservarla
    });
  }, { rootMargin: '50px 0px', threshold: 0.01 });

  immagini.forEach((img) => osservatore.observe(img));
});

Il markup abbinato è semplice: l'immagine reale finisce in data-src, mentre src punta inizialmente a un placeholder leggero, evitando il layout shift quando l'immagine vera arriva.

Una gestione centralizzata delle modali

Quando una pagina ha più finestre modali (conferma, dettaglio, form), ripetere la stessa logica di apertura/chiusura per ognuna porta a codice duplicato e a incoerenze — una modale che non si chiude con Escape, un'altra che non restituisce il focus. Una piccola classe che intercetta gli attributi data-modal risolve il problema una volta per tutte:

class GestoreModali {
  constructor() {
    this.modali = new Map();
    this.inizializza();
  }

  inizializza() {
    document.querySelectorAll('[data-modal]').forEach((trigger) => {
      const id = trigger.dataset.modal;
      const modale = document.getElementById(id);
      if (!modale) return;

      this.modali.set(id, modale);
      trigger.addEventListener('click', (e) => {
        e.preventDefault();
        this.apri(id);
      });
    });

    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape') this.chiudiTutte();
    });
  }

  apri(id) {
    const modale = this.modali.get(id);
    if (!modale) return;
    modale.classList.add('aperta');
    document.body.style.overflow = 'hidden';

    // Sposta subito il focus dentro la modale, per l'utilizzo da tastiera
    const primoFocusabile = modale.querySelector('button, [href], input, textarea, [tabindex]');
    primoFocusabile?.focus();
  }

  chiudiTutte() {
    this.modali.forEach((modale) => modale.classList.remove('aperta'));
    document.body.style.overflow = '';
  }
}

document.addEventListener('DOMContentLoaded', () => {
  window.gestoreModali = new GestoreModali();
});

Un'alternativa più semplice: il tag <dialog>

Se non serve supportare browser molto datati, l'elemento HTML5 <dialog> con .showModal() offre già gratis buona parte di questo comportamento: blocca l'interazione con il resto della pagina, si chiude con Escape e gestisce da solo parte della gestione del focus. Vale la pena valutarlo prima di scrivere una gestione manuale come quella sopra, riservando la classe personalizzata ai casi in cui serva un controllo più fine sull'animazione o sul comportamento di apertura.

Altri articoli su JavaScript grid_viewTutte le tematiche
Scrivici su WhatsApp