back to top

Come usare Intersection Observer per animazioni e lazy load

Introduzione

Le performance sono il nuovo SEO. Google, Core Web Vitals e la crescente attenzione degli utenti ai siti veloci rendono fondamentale alleggerire il JavaScript e rimandare il caricamento di ciò che non serve subito. L’Intersection Observer API (IO API) è arrivata come risposta elegante a due bisogni chiave del frontend moderno:

  1. Animazioni on‑scroll che non dipendano da costosi handler di scroll chiamati a ogni pixel.
  2. Lazy loading di immagini, video o componenti heavy senza janklayout shift.

Se finora hai usato librerie come AOS, Waypoints o plugin jQuery, preparati: con poche righe vanilla JS puoi sostituire tutto, migliorare la Largest Contentful Paint (approfondita qui → Ottimizzare LCP per siti più veloci) e ridurre il bundle. In questa guida pratica—pensata per dev italiani di livello medio‑avanzato—scoprirai il perché tecnico e il come operativo dell’Intersection Observer.

1 · Come funziona tecnicamente Intersection Observer

1.1 Il problema con scroll

Ascoltare l’evento scroll significa eseguire una callback decine di volte al secondo. Anche con debounce/throttle interferisci con il main thread e rischi jank. L’IO API delega tutto al browser.

1.2 Glossario minimo

TermDescrizione
TargetL’elemento da osservare.
RootIl viewport o un container scrollabile. Default: null (= document).
ThresholdValori 0‑1 che indicano quanta parte del target deve essere visibile prima di triggerare la callback.
rootMarginMargine virtuale attorno al root; si imposta come nel CSS ("0px 0px -25% 0px").

1.3 Alta frequenza, basso costo

L’osservatore gira su un thread separato e notifica la callback solo ai cambi di stato di intersezione; il browser può accorpare notifiche e ottimizzare.

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      // ...azione...
    }
  });
}, {
  threshold: 0.25,
  rootMargin: "0px 0px -10% 0px" // pre‑trigger 10% prima di entrare
});

Questo pattern rende elegante anche la disconnessione (observer.unobserve(el)), cruciale per non leakare memory.

Tip: nelle SPA React/Vue rispetta il ciclo di vita (es. useEffect cleanup).

Un approfondimento SEO correlato: Checklist SEO tecnica per sviluppatori frontend.

2 · Esempio pratico 1 — Animare un elemento al viewport

Obiettivo: far comparire un blocco con fade‑in + translate Y solo la prima volta che entra nel viewport.

2.1 Markup minimal

<section class="features">
  <article class="feature-card hidden">
    <h3>Performance al massimo</h3>
    <p>Riduci il _main thread_ con IO API.</p>
  </article>
  <!-- Altre card -->
</section>

2.2 CSS (solo 3 classi!)

.feature-card{
  opacity:1;transform:translateY(0);transition:all .6s ease-out;
}
.hidden{opacity:0;transform:translateY(40px);} /* stato iniziale */

2.3 JavaScript

const cards = document.querySelectorAll('.feature-card');

const revealOnce = new IntersectionObserver((entries, obs) => {
  entries.forEach(entry => {
    if(entry.isIntersecting){
      entry.target.classList.remove('hidden');
      obs.unobserve(entry.target); // reveal one‑shot
    }
  });
},{threshold:0.2});

cards.forEach(card => revealOnce.observe(card));

Perché preferirlo ad AOS? Zero dipendenze, nessun polling, pieno controllo su threshold e rootMargin.

Approfondisci librerie CSS-only: Animazioni CSS moderne: guida pratica.

3 · Esempio pratico 2 — Lazy loading delle immagini

Il tag loading="lazy" copre l’80 % dei casi, ma non sempre: slider custom, Safari < 16, gestione di placeholder. Intersection Observer colma il gap.

3.1 Markup con placeholder

<img class="lazy" data-src="/img/hero.jpg" alt="Esempio Lazy" width="600" height="400" />

3.2 Script lazy‑loader

const lazyImgs = document.querySelectorAll('img.lazy');

const loadImg = (entry) => {
  const img = entry.target;
  img.src = img.dataset.src;
  img.onload = () => img.classList.add('loaded');
};

const ioLazy = new IntersectionObserver((entries, obs)=>{
  entries.forEach(entry=>{
    if(entry.isIntersecting){
      loadImg(entry);
      obs.unobserve(entry.target);
    }
  });
},{rootMargin:'0px 0px 200px 0px'}); // pre‑carica 200 px prima

lazyImgs.forEach(img=> ioLazy.observe(img));

Abbiamo:

  • rootMargin positivo ⇒ pre‑fetch per evitare jank.
  • Disconnessione dopo il caricamento.
  • width/height espliciti per eliminare il cumulative layout shift (CLS).

Consulta anche la guida su Ottimizzare le immagini con picture e srcset per un approccio combinato.

4 · Best practice ed errori da evitare

  1. Sovrappopolare l’Observer » mantieni gruppi omogenei (one observer per tipo).
  2. Threshold = 0 » buono per lazy load, pessimo per animazioni: rischi flicker.
  3. rootMargin errato » Evita valori negativi che tagliano l’area utile.
  4. Dimenticare la disconnessione » unobserve o disconnect in componentWillUnmount/useEffect cleanup.
  5. Accessibilità » le animazioni devono rispettare prefers-reduced-motion.@media (prefers-reduced-motion: reduce){ .feature-card{transition:none;transform:none;} }
  6. SEO » Markup semantico section/articleloading="lazy" + alt testi accurati (vedi HTML Semantico).

Per checklist completa: 5 errori comuni nei progetti frontend e come evitarli.

Conclusione

L’Intersection Observer è la strategia standard per:

  • Eliminare handler di scroll costosi.
  • Creare animazioni eleganti e performant‑friendly.
  • Caricare risorse solo quando servono, migliorando LCP e CLS.

Vuoi spingere oltre le tue animazioni? LeggiAnimazioni CSS moderne: guida pratica e trasforma il tuo UI in esperienza WOW!

Condividi

Articoli Recenti

Categorie popolari