Intersection Observer: Animazioni fluide senza bloccare la pagina

Intersection Observer: Animazioni fluide senza bloccare la pagina

Scritto da Francesco Di Donato 9 luglio 2025 4 minuti di lettura

Il problema: lo “scatto” quando scorri

Hai presente quando un’animazione che si attiva con lo scroll rende la pagina a scatti? La colpa non è (quasi mai) della tua animazione, ma di come la stai attivando.

Il problema è l’evento scroll di JavaScript. Ascoltare questo evento è inefficiente perché costringe il browser a eseguire il tuo codice per ogni singolo pixel di scorrimento.

Questo sovraccarica il main thread, il “cervello” della pagina web che si occupa di tutto: dal rendering della grafica alla gestione del layout, fino all’esecuzione del tuo JavaScript. Quando il main thread è troppo occupato a processare centinaia di eventi scroll, non ha tempo per fare il resto. Risultato? Interfaccia bloccata, animazioni laggose, esperienza utente pessima.


La soluzione: l’Intersection Observer

L’Intersection Observer è l’alternativa moderna ed efficiente. Invece di chiedere continuamente “dove sei?”, dici al browser: “Avvisami solo quando questo elemento entra nello schermo”.

È un approccio asincrono che non blocca il main thread. Il browser fa il lavoro sporco per te, in modo ottimizzato, e invoca la tua funzione (callback) solo nei momenti esatti in cui serve.

Perché è meglio?


Come funziona

Per usare l’Intersection Observer, crei un “osservatore” e gli dici cosa guardare e quando reagire.

Puoi passargli delle options che possono contenere.

const htmlElement = document.querySelector("div");

const observer = new IntersectionObserver(() => {
  // Do something
});
observer.observe(htmlElement);

Non sei tenuto a passare le options. In questo caso, non appena quel <div /> appare nello schermo la callback viene chiamata.

Se vuoi passare opzioni, lo fai come secondo argomento.

new IntersectionObserver(callback, {
  root: someWrapper,
  rootMargin: "10px 20px"
});

Nel lab qui sotto, prova a usare valori come 100px (attiva l’evento 100px prima) o -50px (attiva l’evento 50px dopo che è entrato).

isIntersecting

FALSE

...scorri...
TARGET

Un’altra opzione utile è threshold. Si tratta di una o più soglie numeriche (da 0.0 a 1.0) che definiscono a quale percentuale di visibilità dell’elemento l’osservatore deve reagire.

Puoi anche definire un array di soglie per ricevere notifiche a diversi stadi di visibilità.

const observer = new IntersectionObserver(callback, {
  // Notificami quando l'elemento:
  // - entra (0)
  // - ha fatto un quarto (0.25)
  // - ha percorso metà (0.5)
  // etc.
  threshold: [0, 0.25, 0.5, 0.75, 1],
});

// Questa funzione viene chiamata ogni volta che una soglia viene superata.
function callback([entry]) {
  updateColor(entry.intersectionRatio)
}

Osserva diverse soglie triggerare la callback. Per capire in quale delle soglie ti trovi, puoi interrogare entry.intersectionRatio.

Multiple Thresholds

Visibility0.0%

Thresholds

  • 100%
  • 75%
  • 50%
  • 25%
  • 0%
25%
50%
75%

Esempi pratici

Vediamo due casi d’uso dove l’Intersection Observer brilla.

1. Infinite Scroll

Caricare nuovi contenuti man mano che l’utente si avvicina alla fine della pagina. Con l’Intersection Observer, basta osservare un elemento “trigger” in fondo alla lista. Quando diventa visibile, carichi altri contenuti. Semplice e performante.

List Item 1
List Item 2
List Item 3
List Item 4
List Item 5
List Item 6
List Item 7
List Item 8
List Item 9
List Item 10
List Item 11
List Item 12
List Item 13
List Item 14
List Item 15
List Item 16
List Item 17
List Item 18
List Item 19
List Item 20

2. Lazy Loading delle Immagini

Perché sprecare banda e rallentare il caricamento iniziale per immagini che l’utente potrebbe non vedere mai? Con il “lazy loading”, l’immagine viene scaricata solo quando si avvicina al viewport.

Qui, osserviamo l’immagine e impostiamo una soglia al 50%. Quando l’immagine è visibile per metà, ne avviamo il download e subito dopo smettiamo di osservarla, perché il suo compito è finito.

const image = document.getElementById("my-image");

const observer = new IntersectionObserver(([entry], obs) => {
  // L'immagine è entrata nell'area di osservazione?
  if (entry.isIntersecting) {
    // Carica l'immagine (es. cambiando l'attributo src)
    image.src = image.dataset.src;

    // Smetti di osservare. Il lavoro è finito.
    obs.unobserve(image);
  }
}, {
  threshold: 0.5 // Attivati al 50% di visibilità
});

observer.observe(image);

Scorri verso il basso all'interno di quest'area...

C'è molto testo qui per creare spazio di scorrimento. L'obiettivo è dimostrare che l'immagine sottostante non verrà caricata finché non sarà visibile per almeno la metà.

Continuando a scorrere, ti avvicinerai all'immagine. Tieni d'occhio la scheda "Network" negli strumenti per sviluppatori del tuo browser per vedere la richiesta partire solo al momento giusto.



Segnaposto sfocato


Ecco l'immagine! Se hai visto un segnaposto sfocato per un istante, significa che il lazy loading ha funzionato correttamente.

Questo approccio è estremamente utile per pagine con molte immagini, come gallerie o feed di prodotti, perché riduce drasticamente il tempo di caricamento iniziale della pagina e il consumo di dati.

Pulizia

Ricordati sempre di rimuovere l’observer quando non serve più. Puoi usare observer.disconnect().