back to top

ResizeObserver: componenti veramente fluidi

Il responsive design è ormai uno standard consolidato nello sviluppo web moderno. Per anni, le media query CSS sono state lo strumento principale per adattare il layout e lo stile dei siti web alle diverse dimensioni del viewport. Funzionano benissimo per cambiare la struttura generale della pagina o la presentazione di blocchi ampi in base allo schermo dell’utente. Per approfondire l’uso classico delle media query, puoi consultare la Guida Completa al Responsive Design con Media Query CSS.

Tuttavia, cosa succede quando non è la dimensione della finestra del browser a dettare il comportamento di un componente, ma la dimensione dello spazio che il componente occupa? In layout complessi basati su CSS Grid o Flexbox, o quando i componenti possono essere riutilizzati in contesti diversi con spazi disponibili variabili, le media query legate al viewport diventano insufficienti. Abbiamo bisogno di una reattività basata sull’elemento stesso.

È qui che entra in gioco l’API ResizeObserver JavaScript, una soluzione potente e flessibile per costruire componenti che sono veramente fluidi e reattivi alla propria dimensione, non solo a quella del viewport. Questo articolo esplorerà cos’è ResizeObserver, come funziona e perché rappresenta il futuro per un design web veramente adattivo a livello di singolo componente.

Cos’è la reattività basata sulla dimensione reale dell’elemento?

Immaginate una card con un’immagine, un titolo e un testo descrittivo. In una colonna laterale stretta, vorreste che l’immagine fosse piccola e il testo compatto. Nella colonna principale, più larga, l’immagine potrebbe ingrandirsi e il testo magari disporsi diversamente. Le media query sul viewport potrebbero gestire questo se la colonna stessa cambia larghezza solo a specifici breakpoint del viewport.

Ma se la larghezza della colonna (e quindi della card al suo interno) dipendesse da altri fattori, come la presenza di altre colonne che l’utente può espandere/comprimere via JavaScript, o se la card è inserita in un layout CSS Grid dove le dimensioni delle celle sono dinamiche? In questi scenari, il viewport non cambia, ma la dimensione disponibile per la card sì.

La reattività basata sulla dimensione dell’elemento significa che il componente (la nostra card, un grafico, un blocco di testo) è in grado di rilevare i cambiamenti nella propria larghezza o altezza e adattare di conseguenza il suo contenuto o il suo layout interno, indipendentemente dalla dimensione globale della finestra del browser. Questo è fondamentale per creare sistemi di design modulari e componenti riutilizzabili che si comportano in modo intelligente ovunque vengano posizionati.

Perché ResizeObserver è il futuro per il design adattivo

Per lungo tempo, l’unico modo per ottenere questo tipo di reattività basata sull’elemento era agganciare un listener all’evento window.resize e poi, all’interno del listener, iterare sugli elementi di interesse, leggerne la dimensione attuale (element.offsetWidth, element.offsetHeight) e applicare le modifiche necessarie. Questo approccio presenta diversi svantaggi:

  1. Inefficienza: L’evento window.resize si attiva solo quando il viewport cambia dimensione. Non viene attivato se un elemento cambia dimensione per altri motivi (modifiche del contenuto, layout dinamico, ecc.).
  2. Performance: Il listener su window.resize può attivarsi molto frequentemente durante il ridimensionamento. Iterare su molti elementi e leggere le loro dimensioni può causare costose letture di layout che peggiorano le performance di rendering, specialmente su pagine complesse. Per tecniche di ottimizzazione JavaScript più generali, puoi leggere la Guida alla JavaScript Performance Optimization.
  3. Complessità: Gestire logicamente quale elemento è cambiato e perché all’interno di un singolo listener globale diventa rapidamente complesso e difficile da mantenere.

ResizeObserver risolve intrinsecamente questi problemi. Fornisce un meccanismo efficiente e mirato per essere notificati solo quando la dimensione di uno specifico elemento che stiamo osservando cambia. Non dobbiamo più affidarci all’evento globale del viewport o leggere manualmente le dimensioni degli elementi in modo potenzialmente inefficiente, migliorando significativamente le performance.

Questa API è anche la base su cui sono state costruite le Container Queries CSS, una feature molto attesa che porterà la reattività basata sull’elemento direttamente nel CSS. Puoi esplorare le Container Queries e le loro best practice. Tuttavia, ResizeObserver offre maggiore flessibilità quando la logica di adattamento richiede JavaScript (ad esempio, per ricalcolare posizioni, caricare immagini diverse, o manipolare il DOM in modi complessi che il solo CSS non può fare).

Cos’è ResizeObserver

ResizeObserver è un’API Web che consente di monitorare le modifiche alle dimensioni degli elementi DOM. È concettualmente simile ad altre API di osservazione come IntersectionObserver o MutationObserver, ma specificamente focalizzato sulla dimensione degli elementi nel modello DOM (Document Object Model).

Come funziona

Il funzionamento è basato su un oggetto ResizeObserver e una funzione di callback:

Si crea una nuova istanza di ResizeObserver, passando una funzione di callback al costruttore:

const observer = new ResizeObserver(entries => {
  // La funzione di callback viene eseguita quando la dimensione di un elemento osservato cambia.
  for (const entry of entries) {
    // entry contiene informazioni sull'elemento che è cambiato
    console.log('Elemento cambiato:', entry.target);
    console.log('Nuove dimensioni:', entry.contentRect.width, 'x', entry.contentRect.height);

    // Esempio: applica una classe CSS se la larghezza è inferiore a un certo valore
    if (entry.contentRect.width < 300) {
      entry.target.classList.add('small-size');
    } else {
      entry.target.classList.remove('small-size');
    }
  }
});

Si specifica quale elemento o quali elementi si vogliono osservare usando il metodo observe():

const myElement = document.querySelector('#my-resizable-element');
observer.observe(myElement);

Si possono osservare più elementi con la stessa istanza di ResizeObserver.

Quando la dimensione dell’elemento osservato (myElement nel nostro esempio) cambia, la funzione di callback viene invocata. L’argomento entries è un array di oggetti ResizeObserverEntry, uno per ogni elemento osservato la cui dimensione è cambiata in quel ciclo di rendering.

Ogni ResizeObserverEntry contiene:

target: L’elemento DOM la cui dimensione è cambiata.

contentRect: Un oggetto DOMRectReadOnly che descrive il content box (l’area interna al padding e al bordo) dell’elemento. Le proprietà più utili sono width e height.

borderBoxSize, contentBoxSize, devicePixelContentBoxSize: Offrono modi più precisi e moderni per accedere alle dimensioni del bordo, del contenuto e del contenuto in pixel del dispositivo. Sono disponibili come array di oggetti con inlineSize e blockSize. È consigliato usare queste proprietà più recenti se la compatibilità con browser molto vecchi non è una priorità assoluta. Ad esempio, entry.borderBoxSize[0].inlineSize per la larghezza nel layout direzionale del documento.

Quando non si vuole più osservare un elemento, si usa unobserve():

observer.unobserve(myElement);

Per interrompere l’osservazione di tutti gli elementi da parte di un’istanza, si usa disconnect():

observer.disconnect();

Questo è importante per prevenire memory leak, specialmente in Single Page Application dove i componenti vengono montati e smontati.

Supporto Browser e Fallback

Il supporto per ResizeObserver è eccellente nei browser moderni (Chrome, Firefox, Safari, Edge, Opera, ecc.). Per garantire la compatibilità con browser legacy che non lo supportano (come Internet Explorer, ormai poco rilevante, ma utile saperlo per progetti specifici), è possibile utilizzare un polyfill. Un polyfill comune è disponibile tramite librerie o può essere incluso direttamente nel progetto.

È buona norma, quando si usa un’API moderna, considerare una strategia di fallback o progressive enhancement se il supporto completo è critico per l’esperienza utente. Tuttavia, per molte applicazioni, specialmente quelle che si rivolgono a un pubblico con browser aggiornati, l’implementazione di ResizeObserver JavaScript può essere usata direttamente.

Quando è utile rispetto a window.resize

Abbiamo già toccato questo punto, ma è cruciale ribadire:

  • Usa window.resize (o meglio, le media query CSS che sono l’uso principale dell’evento resize, come visto nella Guida Completa alle Media Query CSS) quando devi cambiare il layout o lo stile dell’intera pagina o di blocchi principali in base alla dimensione del viewport.
  • Usa ResizeObserver JavaScript quando un singolo elemento o un gruppo di elementi deve cambiare il suo comportamento interno o il suo aspetto perché lo spazio a sua disposizione (la sua dimensione) è cambiato, indipendentemente dal viewport. Questo è il caso ideale in componenti riutilizzabili, layout dinamici (CSS Grid vs Flexbox), dashboard con widget ridimensionabili, o quando il contenuto stesso di un elemento modifica la sua dimensione.

Esempio pratico: Card Fluida con ResizeObserver

Vediamo un esempio concreto di come usare ResizeObserver JavaScript per rendere una card “fluida”, modificando la dimensione del font del titolo in base alla larghezza disponibile per la card stessa.

Supponiamo di avere una card con la seguente struttura HTML:

<div class="container">
  <div class="card">
    <img src="placeholder.jpg" alt="Immagine di esempio">
    <div class="card-content">
      <h3 class="card-title">Titolo della Card</h3>
      <p class="card-text">Questo è un testo di esempio per la card. La dimensione di questo testo e del titolo si adatterà alla larghezza disponibile per la card.</p>
    </div>
  </div>
</div>

E un po’ di CSS di base:

.container {
  width: 80%; /* Un container che può cambiare dimensione */
  margin: 20px auto;
  padding: 20px;
  border: 1px solid #ccc;
  resize: horizontal; /* Rende il container ridimensionabile dall'utente (per test) */
  overflow: auto;
  min-width: 200px;
  max-width: 90%;
}

.card {
  display: flex;
  border: 1px solid #eee;
  margin-bottom: 15px;
  background-color: #fff;
  box-shadow: 2px 2px 5px rgba(0,0,0,0.1);
  flex-direction: row; /* Default: immagine a sinistra, contenuto a destra */
}

.card img {
  width: 150px;
  height: auto;
  object-fit: cover;
}

.card-content {
  padding: 15px;
  flex-grow: 1;
}

.card-title {
  margin-top: 0;
  /* La dimensione del font sarà gestita via JS/ResizeObserver */
}

.card-text {
   /* La dimensione del font sarà gestita via JS/ResizeObserver */
}

/* Classi CSS che applicheremo dinamicamente con ResizeObserver */
.card.medium-size {
  /* Stili per larghezza media */
}

.card.small-size {
  flex-direction: column; /* Su dimensioni piccole, l'immagine va sopra il testo */
}

.card.small-size img {
  width: 100%; /* Immagine a piena larghezza */
  height: 100px; /* Altezza fissa o proporzionale */
}

.card.small-size .card-title {
  font-size: 1em; /* Font più piccolo */
}

.card.small-size .card-text {
  font-size: 0.9em; /* Font più piccolo */
}

Ora, il JavaScript che utilizza ResizeObserver:

// Seleziona l'elemento card che vogliamo osservare
const cardElement = document.querySelector('.card');

// Seleziona gli elementi interni il cui stile cambierà
const cardTitle = cardElement.querySelector('.card-title');
const cardText = cardElement.querySelector('.card-text');

// Definisci la funzione di callback per ResizeObserver
const handleCardResize = (entries) => {
  // entries è un array, ma in questo caso osserviamo un solo elemento
  const entry = entries[0];

  // Ottieni la larghezza attuale dell'elemento osservato (la card)
  const cardWidth = entry.contentRect.width;
  // Puoi anche usare: const cardWidth = entry.borderBoxSize[0].inlineSize; // Metodo più moderno

  console.log(`La card ha ora larghezza: ${cardWidth}px`);

  // Applica stili o classi in base alla larghezza
  if (cardWidth < 300) {
    // Card molto stretta
    cardElement.classList.add('small-size');
    cardElement.classList.remove('medium-size');
    cardTitle.style.fontSize = '1em'; // Modifica diretta dello stile
    cardText.style.fontSize = '0.9em';
  } else if (cardWidth < 500) {
    // Card di larghezza media
    cardElement.classList.remove('small-size');
    cardElement.classList.add('medium-size');
    cardTitle.style.fontSize = '1.2em';
    cardText.style.fontSize = '1em';
  } else {
    // Card larga
    cardElement.classList.remove('small-size');
    cardElement.classList.remove('medium-size');
    // Ripristina stili di default o imposta stili per larghezza grande
    cardTitle.style.fontSize = '1.5em';
    cardText.style.fontSize = '1.1em';
  }
};

// Crea un'istanza di ResizeObserver con la nostra callback
const observer = new ResizeObserver(handleCardResize);

// Inizia ad osservare l'elemento card
observer.observe(cardElement);

// IMPORTANTE: Pulisci l'observer quando l'elemento non è più nel DOM
// (Questo è cruciale in app SPA o con elementi aggiunti/rimossi dinamicamente)
// Ad esempio, se stai usando un framework JS con un ciclo di vita:
/*
useEffect(() => {
  const cardElement = ref.current; // Supponiamo tu abbia un ref all'elemento

  if (cardElement) {
    const observer = new ResizeObserver(handleCardResize);
    observer.observe(cardElement);

    return () => {
      // Pulizia al dismount del componente
      observer.unobserve(cardElement);
      observer.disconnect();
    };
  }
}, []);
*/

In questo esempio:

  1. Abbiamo un container ridimensionabile (per dimostrare che la card cambia dimensione senza che il viewport cambi).
  2. La card ha un layout interno flessibile (flexbox).
  3. Creiamo un ResizeObserver che guarda solo la card.
  4. La funzione handleCardResize viene chiamata ogni volta che la dimensione della card cambia.
  5. All’interno della callback, leggiamo la larghezza della card (entry.contentRect.width).
  6. Usiamo semplici condizionali if/else if per controllare la larghezza e applichiamo classi CSS (small-size, medium-size) o modifichiamo direttamente le proprietà fontSize del titolo e del testo.

Questo dimostra come la card reagisce alla sua larghezza disponibile, non a quella del viewport. Se ridimensionate il container, vedrete la card cambiare layout e font size di conseguenza.

Vantaggi rispetto alle media query

Ricapitolando, i principali vantaggi di usare ResizeObserver JavaScript rispetto alle sole media query basate sul viewport (di cui puoi leggere la Guida Completa alle Media Query CSS) sono:

  1. Maggiore granularità: Le media query agiscono sulla pagina intera o su blocchi di alto livello in base al viewport globale. ResizeObserver consente di rendere ogni singolo componente reattivo alla propria dimensione.
  2. Supporto a layout dinamici: Perfetto per componenti all’interno di griglie CSS, layout Flexbox, o qualsiasi altro scenario in cui le dimensioni degli elementi sono determinate dal contesto e non sono direttamente legate a breakpoint fissi del viewport.
  3. Componenti riutilizzabili: Permette di creare componenti che si adattano in modo intelligente ovunque vengano inseriti nella pagina, senza la necessità di scrivere media query specifiche per ogni contesto in cui potrebbero apparire.
  4. Container Queries (complementare): Sebbene le Container Queries CSS stiano arrivando per risolvere molti di questi problemi lato CSS, ResizeObserver rimane uno strumento potente per i casi in cui la logica di adattamento è complessa e richiede la potenza di JavaScript. Di fatto, ResizeObserver è l’API fondamentale su cui si basano le Container Queries.

Considerazioni su performance e ottimizzazione

Sebbene ResizeObserver sia progettato per essere molto più efficiente del polling manuale o del listener su window.resize unito alla lettura di offsetWidth/offsetHeight, ci sono comunque delle best practice da seguire per garantire performance ottimali, in linea con i concetti di JavaScript Performance Optimization:

  • Attenzione alle modifiche recursive: Evitate di modificare una proprietà dell’elemento osservato all’interno della callback che potrebbe a sua volta causarne il ridimensionamento (es. cambiare padding, border o width sull’elemento osservato). Sebbene l’API abbia delle salvaguardie per prevenire loop infiniti, questo può portare a multiple invocazioni della callback nello stesso frame e peggiorare le performance. Se dovete modificare l’elemento, fatelo in modo che non scateni un nuovo resize observer event, o modificate un elemento diverso basandovi sulla dimensione dell’elemento osservato (come nell’esempio della card, dove cambiamo font size o classi che alterano il flexbox, ma non modifichiamo direttamente la width in modo che causi un loop ovvio).
  • Debounce o Throttle (se necessario): La callback di ResizeObserver viene invocata prima del rendering di ogni frame in cui sono avvenute modifiche di dimensione. Durante un ridimensionamento continuo (es. trascinando il bordo di una finestra), la callback può essere chiamata molte volte al secondo. Se le operazioni all’interno della callback sono computazionalmente pesanti (calcoli complessi, manipolazioni DOM estese), considerate l’uso di tecniche di debouncing o throttling per limitare la frequenza di esecuzione della callback. Una funzione di debounce posticiperà l’esecuzione finché il ridimensionamento non si è fermato per un breve periodo; una funzione di throttle limiterà l’esecuzione a un massimo di N volte al secondo.
  • Osservare solo ciò che è necessario: Non osservare elementi che non cambiano dimensione o per cui non avete bisogno di reattività basata sulla loro dimensione.
  • Disconnettere gli observer: Assicuratevi di disconnettere (unobserve o disconnect) gli observer quando gli elementi osservati vengono rimossi dal DOM, specialmente in applicazioni a pagina singola dove i componenti vengono creati e distrutti dinamicamente. Questo previene memory leak.

Limiti e best practice

  • Non osserva modifiche a transform: ResizeObserver non reagisce a cambiamenti di dimensione causati da trasformazioni CSS (transform: scale(...)). Osserva le dimensioni del layout box dell’elemento prima che vengano applicate le trasformazioni.
  • Box Sizing: Tenete presente come box-sizing ( content-box vs border-box) influisce sulle dimensioni riportate da contentRect (solo contenuto) rispetto a borderBoxSize (bordo incluso). Usate la proprietà più adatta al vostro caso.
  • Elementi nascosti: Gli elementi nascosti (con display: none) non generano eventi ResizeObserver. Se la loro dimensione cambia mentre sono nascosti e poi diventano visibili, l’evento verrà generato al momento in cui diventano visibili e il browser calcola le loro dimensioni effettive.

FAQ Tecniche

  • Cos’è ResizeObserver in JavaScript? È un’API JavaScript che permette agli sviluppatori di essere notificati ogni volta che la dimensione di un elemento DOM osservato cambia.
  • Quando usare ResizeObserver invece di media query? Usalo quando un componente deve reagire alla propria dimensione disponibile nel layout, indipendentemente dalla dimensione globale del viewport. È ideale per layout dinamici e componenti riutilizzabili non gestibili con le classiche Media Query CSS.
  • È compatibile con tutti i browser? Ha un ottimo supporto nei browser moderni. Per la compatibilità con browser più vecchi, è necessario utilizzare un polyfill.
  • Come posso migliorare le performance usando ResizeObserver? Evita modifiche recursive che causano nuovi resize sull’elemento osservato. Considera l’uso di tecniche di debouncing o throttling per operazioni pesanti nella callback se il ridimensionamento avviene frequentemente, seguendo i principi di JavaScript Performance Optimization. Disconnetti gli observer quando non servono più.
  • ResizeObserver funziona anche per l’altezza? Sì, la callback fornisce sia la larghezza che l’altezza dell’elemento tramite le proprietà dell’entry (es. entry.contentRect.height o entry.borderBoxSize[0].blockSize).

Conclusione

ResizeObserver JavaScript è uno strumento essenziale per lo sviluppo frontend moderno. Ci permette di superare i limiti delle media query basate sul viewport e di costruire componenti veramente adattivi che rispondono in modo intelligente allo spazio che occupano sulla pagina. Che si tratti di adattare la tipografia (magari in combinazione con le funzioni CSS min(), max(), clamp()), cambiare il layout interno di un widget, o ottimizzare il caricamento di risorse in base allo spazio disponibile, ResizeObserver offre un meccanismo performante e pulito per gestire queste esigenze.

Iniziate a sperimentare con ResizeObserver nei vostri progetti per rendere i vostri componenti più robusti e flessibili. Combinato con le future Container Queries CSS, trasformerà radicalmente il modo in cui concepiamo e implementiamo il responsive design.

Risorse Utili:

Condividi

Articoli Recenti

Categorie popolari