W3docs

JavaScript Resize Observer API

Scopri la JavaScript Resize Observer API per reagire ai cambiamenti di dimensione degli elementi — per componenti responsive, canvas, grafici e input che crescono automaticamente.

L'API ResizeObserver ti permette di reagire quando un elemento specifico cambia dimensione, indipendentemente dal motivo. Appartiene alla stessa famiglia del MutationObserver (che osserva l'albero DOM) e dell'IntersectionObserver (che osserva la visibilità): observer efficienti e basati su callback che sostituiscono i cicli di polling macchinosi.

Perché Non Usare Semplicemente l'Evento resize?

L'evento resize della window si attiva solo quando cambia la dimensione della finestra del browser. Ma un elemento sulla pagina può cambiare dimensione per molti altri motivi:

  • Una regola CSS cambia (entra in gioco una media query, viene attivata o disattivata una classe).
  • Il suo contenuto cambia (viene caricato del testo, arriva un'immagine, vengono aggiunte righe a un elenco).
  • Un layout Flexbox o CSS Grid viene ricalcolato perché un elemento adiacente è cresciuto o si è ridotto.
  • Un contenitore padre viene ridimensionato da uno splitter trascinabile, una barra laterale o un pannello.

Nessuno di questi ridimensiona necessariamente la window, quindi window.addEventListener('resize', ...) non si attiva mai. Potresti fare polling con setInterval e confrontare getBoundingClientRect() ogni pochi millisecondi, ma questo spreca CPU e rimane comunque in ritardo rispetto al cambiamento reale.

ResizeObserver risolve questo problema: osserva uno o più elementi e ti richiama solo quando la loro dimensione cambia effettivamente.

Informazione

ResizeObserver segnala la dimensione del contenuto di un elemento per impostazione predefinita, non la sua posizione. Se devi sapere quando un elemento si sposta o scorre nella visualizzazione, usa invece IntersectionObserver. Per le misurazioni a livello di viewport, vedi dimensioni della finestra e scorrimento.

Utilizzo di Base

Lo schema rispecchia gli altri observer: crea un observer con una callback, poi indica quali elementi observe.

const box = document.querySelector('#box');

const ro = new ResizeObserver((entries) => {
  for (const entry of entries) {
    const { width, height } = entry.contentRect;
    console.log(`${entry.target.id} is now ${width} x ${height}`);
  }
});

ro.observe(box);

La callback riceve un array di entry — una per ogni elemento osservato che è cambiato in questo batch — quindi un singolo observer può monitorare molti elementi contemporaneamente.

Cosa Contiene una Entry

Ogni entry descrive un elemento e la sua nuova dimensione. Ci sono due modi per leggere tale dimensione.

Il modo semplice è entry.contentRect, un DOMRectReadOnly con width, height, top e left (gli offset sono relativi al padding box dell'elemento):

const ro = new ResizeObserver((entries) => {
  for (const entry of entries) {
    console.log(entry.target);          // the element
    console.log(entry.contentRect.width);  // content-box width in px
    console.log(entry.contentRect.height); // content-box height in px
  }
});

Il modo più preciso utilizza le proprietà delle dimensioni del box, che sono array di object { inlineSize, blockSize } (un array perché un elemento può essere frammentato su più colonne):

  • entry.contentBoxSize — il content box, esclusi padding e bordo.
  • entry.borderBoxSize — il border box, inclusi padding e bordo.
  • entry.devicePixelContentBoxSize — il content box misurato in pixel dispositivo, ideale per un rendering nitido del canvas su schermi ad alta risoluzione (high-DPI).
const ro = new ResizeObserver((entries) => {
  for (const entry of entries) {
    // inlineSize ~ width, blockSize ~ height (for a horizontal writing mode)
    const { inlineSize, blockSize } = entry.borderBoxSize[0];
    console.log(`border box: ${inlineSize} x ${blockSize}`);
  }
});

Per impostazione predefinita l'observer reagisce alle modifiche nel content box. Per osservare invece il border box, passa un object di opzioni:

ro.observe(box, { box: 'border-box' });
Nota

inlineSize e blockSize sono consapevoli della modalità di scrittura. Nella modalità predefinita da sinistra a destra e dall'alto verso il basso, inlineSize corrisponde alla larghezza e blockSize all'altezza — ma in una modalità di scrittura verticale si invertono. Preferiscili rispetto a width/height hardcoded quando la tua interfaccia deve supportare più direzioni di scrittura.

Metodi

ResizeObserver espone tre metodi:

  • observe(element, options) — inizia a osservare un elemento. Chiamalo una volta per elemento. L'options.box opzionale può essere 'content-box' (predefinito), 'border-box' o 'device-pixel-content-box'.
  • unobserve(element) — smetti di osservare un singolo elemento.
  • disconnect() — smetti di osservare tutti gli elementi contemporaneamente.
ro.observe(el);        // start
ro.unobserve(el);      // stop watching this one
ro.disconnect();       // stop watching everything

Caso d'Uso 1 — Componenti Responsive ("Container Query in JS")

Una media query reagisce al viewport. Ma una card riutilizzabile potrebbe essere larga nella colonna principale e stretta in una barra laterale, alla stessa larghezza di viewport. Con ResizeObserver puoi adattare un componente alla propria larghezza:

const card = document.querySelector('.card');

const ro = new ResizeObserver(([entry]) => {
  const width = entry.contentRect.width;
  // Toggle a layout class based on the element's own width.
  card.classList.toggle('card--compact', width < 400);
});

ro.observe(card);
.card { display: flex; gap: 1rem; }
.card--compact { flex-direction: column; }

Ora la card si dispone verticalmente ogni volta che essa stessa è più stretta di 400px, indipendentemente dalla dimensione della window — utile in dashboard, pannelli divisi e widget incorporabili.

Suggerimento

I CSS moderni possono fare questo nativamente con le container query (@container). Se hai solo bisogno di ridefinire lo stile in base alla dimensione di un contenitore, preferisci le CSS container query — sono dichiarative e vengono eseguite fuori dal thread principale. Ricorri a ResizeObserver quando devi eseguire JavaScript in risposta al cambiamento di dimensione (ricalcolare valori, ridisegnare un canvas, riorganizzare un grafico).

Caso d'Uso 2 — Ridimensionamento di un Canvas o di un Grafico

Un <canvas> ha due dimensioni: la sua dimensione di visualizzazione CSS e la dimensione del buffer di disegno (canvas.width/canvas.height). Se non corrispondono, la bitmap viene allungata e appare sfocata. ResizeObserver mantiene il buffer sincronizzato con la dimensione visualizzata in modo che i disegni rimangano nitidi:

const canvas = document.querySelector('#chart');
const ctx = canvas.getContext('2d');

const ro = new ResizeObserver(([entry]) => {
  // Use device-pixel size for sharp rendering on high-DPI screens.
  const size = entry.devicePixelContentBoxSize?.[0];
  const width = size ? size.inlineSize : entry.contentRect.width;
  const height = size ? size.blockSize : entry.contentRect.height;

  canvas.width = width;
  canvas.height = height;
  redraw(ctx, width, height); // your drawing / charting code
});

ro.observe(canvas, { box: 'device-pixel-content-box' });

La stessa idea si applica alle librerie per grafici: osserva il contenitore del grafico e chiama il metodo resize() della libreria quando il contenitore cambia, invece di agganciare solo window.resize.

Caso d'Uso 3 — Textarea che Cresce Automaticamente

Poiché la callback si attiva ogni volta che la dimensione misurata dell'elemento cambia, puoi mantenere sincronizzati gli elementi dipendenti. Un esempio classico è specchiare l'altezza di una textarea su un elemento adiacente, o reagire alla crescita guidata dal contenuto:

const textarea = document.querySelector('#message');
const counter = document.querySelector('#height-readout');

const ro = new ResizeObserver(([entry]) => {
  const h = Math.round(entry.contentRect.height);
  counter.textContent = `${h}px tall`;
});

ro.observe(textarea);

Ogni volta che l'utente trascina il punto di ridimensionamento della textarea — o il codice ne modifica l'altezza man mano che l'utente digita — il contatore si aggiorna automaticamente, senza eventi resize e senza polling.

L'Avviso "ResizeObserver loop"

Potresti imbatterti in questo messaggio in console:

ResizeObserver loop completed with undelivered notifications.

(Alcuni browser lo formulano come "ResizeObserver loop limit exceeded.") Accade quando la tua callback cambia la dimensione dell'elemento osservato, il che attiva di nuovo l'observer, che cambia di nuovo la dimensione — un ciclo di feedback.

// Anti-pattern: this can cause the loop warning.
const ro = new ResizeObserver(([entry]) => {
  // Resizing the observed element from inside its own callback. Bad.
  entry.target.style.height = entry.contentRect.width + 'px';
});
Attenzione

Non ridimensionare in modo sincrono l'elemento osservato all'interno della sua stessa callback. Se un ridimensionamento guidato dalla dimensione è inevitabile, spezza il ciclo: scrivi solo quando il valore è effettivamente cambiato (una guardia), oppure rinvia la scrittura con requestAnimationFrame. L'avviso è solitamente innocuo e si autocorregge, ma un vero ciclo infinito peggiorerà le prestazioni.

Pulizia

Un observer mantiene un riferimento a ogni elemento che osserva, il che può impedire a quell'elemento di essere sottoposto a garbage collection. Smetti sempre di osservare quando hai finito — ad esempio, quando un componente viene smontato o una vista viene distrutta:

function mountWidget(el) {
  const ro = new ResizeObserver(handleResize);
  ro.observe(el);

  // Return a cleanup function.
  return () => ro.disconnect();
}

Dimenticare questo è una fonte comune di memory leak nelle single-page app. Per ulteriori informazioni su come mantenere economico il lavoro di dimensionamento e layout, vedi ottimizzazione delle prestazioni del DOM.

ResizeObserver è supportato in tutti i browser moderni, quindi puoi usarlo senza polyfill in qualsiasi browser evergreen attuale.

Metti alla Prova le Tue Conoscenze

Pratica
Perché preferire ResizeObserver all'evento 'resize' della window per un componente?
Perché preferire ResizeObserver all'evento 'resize' della window per un componente?
Pratica
Cosa fornisce ogni entry di ResizeObserver?
Cosa fornisce ogni entry di ResizeObserver?
Pratica
Cosa scatena comunemente l'avviso 'ResizeObserver loop'?
Cosa scatena comunemente l'avviso 'ResizeObserver loop'?
Was this page helpful?