</> HTML5Advent
ENFRESDEITPT

// apis · Web Platform Advent #10

L'API IntersectionObserver: lazy-loading ed effetti allo scroll

Usa IntersectionObserver per caricare immagini in lazy-loading, rivelare elementi durante lo scroll e creare lo scroll infinito — con threshold e rootMargin spiegati, più esempi eseguibili.

Vista dall'alto della scrivania di un designer con schizzi di wireframe UI, foglietti adesivi, uno smartphone e pennarelli colorati

L'API IntersectionObserver ti dice quando un elemento entra o esce dal viewport — senza che tu debba collegare un listener scroll e calcolare le posizioni a ogni frame. Il browser esegue il controllo in modo asincrono e fuori dal thread principale, quindi tutto resta fluido anche con centinaia di elementi osservati. Ogni browser attuale la supporta.

Perché non ascoltare semplicemente lo scroll?

Un handler scroll scatta di continuo e ti costringe a chiamare getBoundingClientRect() per sapere dove si trovano gli elementi — questo legge il layout e può causare scatti. IntersectionObserver ribalta il modello: registri gli elementi che ti interessano e il browser ti avvisa solo quando la loro visibilità cambia davvero.

Lo schema di base

Crea un observer con una callback, poi chiama observe() su uno o più elementi. La callback riceve un array di oggetti entry; la proprietà chiave è entry.isIntersecting:

const observer = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    if (entry.isIntersecting) {
      console.log('Now visible:', entry.target);
    }
  }
});

document.querySelectorAll('.watch').forEach((el) => observer.observe(el));

Lazy-loading delle immagini

Un uso classico: caricare un'immagine solo quando sta per entrare nella vista. Memorizza l'URL reale in data-src, scambialo quando l'elemento interseca, poi smetti di osservarlo:

const lazyImages = document.querySelectorAll('img[data-src]');

const imgObserver = new IntersectionObserver((entries, observer) => {
  for (const entry of entries) {
    if (!entry.isIntersecting) continue;
    const img = entry.target;
    img.src = img.dataset.src;
    observer.unobserve(img); // load once, then stop watching
  }
}, { rootMargin: '200px' });

lazyImages.forEach((img) => imgObserver.observe(img));

Nota: il lazy-loading nativo tramite <img loading="lazy"> copre ormai la maggior parte dei casi. Ricorri a IntersectionObserver quando ti serve una logica personalizzata — segnaposto, dissolvenze o il caricamento di risorse non immagine.

Uno smartphone che mostra la schermata di login di Facebook accanto a tessere dello Scrabble che compongono le parole SOCIAL MEDIA
I feed a scroll infinito come le app dei social media sono un caso da manuale per IntersectionObserver: osserva un elemento sentinella vicino al fondo e carica altri contenuti quando appare.

Rivelare elementi durante lo scroll

Aggiungi una classe CSS quando un elemento diventa visibile per la prima volta, così può animarsi all'ingresso. Usa l'opzione threshold per decidere quanto dell'elemento deve essere visibile prima che la callback scatti:

const revealObserver = new IntersectionObserver((entries, observer) => {
  for (const entry of entries) {
    if (entry.isIntersecting) {
      entry.target.classList.add('is-visible');
      observer.unobserve(entry.target);
    }
  }
}, { threshold: 0.2 }); // fire when 20% is on screen

document.querySelectorAll('.reveal').forEach((el) => revealObserver.observe(el));

Scroll infinito

Posiziona un elemento sentinella vuoto dopo la tua lista. Quando entra nel viewport, recupera e accoda la pagina successiva, poi continua a osservare per la pagina seguente:

const sentinel = document.querySelector('#sentinel');
let page = 1;

const loader = new IntersectionObserver(async (entries) => {
  if (!entries[0].isIntersecting) return;
  const items = await fetchPage(page++);
  appendItems(items);
});

loader.observe(sentinel);

Le opzioni: threshold e rootMargin

Due opzioni determinano quando scatta la callback:

  • threshold — un numero (o array) da 0 a 1. 0 scatta nel momento in cui un pixel è visibile; 1 scatta solo quando l'intero elemento è visibile. Un array come [0, 0.5, 1] scatta a ogni passo, comodo per gli effetti di avanzamento dello scroll.
  • rootMargin — un margine in stile CSS che ingrandisce o riduce l'area usata per il test. '200px' si attiva 200px prima che l'elemento raggiunga il viewport — ideale per il pre-caricamento.
  • root — l'elemento da usare come viewport. Per impostazione predefinita è il viewport del browser; impostalo su un contenitore scorrevole per osservare all'interno di esso.

Pulizia

Smetti di osservare un singolo elemento con observer.unobserve(el), oppure smonta tutto con observer.disconnect() — importante nelle single-page app quando una vista viene smontata, per evitare perdite di memoria.

Riferimento rapido

ObiettivoOpzione / chiamata chiave
Rilevare la visibilitàentry.isIntersecting
Pre-caricare in anticiporootMargin: '200px'
Attendere finché X% è visibilethreshold: 0.5
Osservare dentro un contenitoreroot: containerEl
Fermarne unoobserver.unobserve(el)
Smontare tuttoobserver.disconnect()

IntersectionObserver sostituisce un'intera categoria di fragili calcoli di scroll con una piccola API dichiarativa. Immagini lazy, rivelazioni allo scroll e liste infinite si riducono tutte allo stesso schema: osservare, reagire a isIntersecting e smettere di osservare quando hai finito.