RidimensionaObservationr: è come document.onresize per gli elementi

ResizeObserver ti consente di sapere quando le dimensioni di un elemento cambiano.

Prima di ResizeObserver, dovevi associare un ascoltatore all'evento resize del documento per ricevere una notifica di qualsiasi modifica delle dimensioni della visualizzazione. Nel gestore di eventi, dovresti quindi capire quali elementi sono stati interessati da tale modifica e chiamare una routine specifica per reagire in modo appropriato. Se avevi bisogno delle nuove dimensioni di un elemento dopo una modifica delle dimensioni, dovevi chiamare getBoundingClientRect() o getComputedStyle(), il che può causare un errato ordinamento del layout se non ti occupi di raggruppare tutte le letture e tutte le scritture.

Inoltre, non erano coperti nemmeno i casi in cui le dimensioni degli elementi cambiavano senza che la finestra principale fosse stata ridimensionata. Ad esempio, l'aggiunta di nuovi elementi secondari, l'impostazione dello stile display di un elemento su none o azioni simili possono modificare le dimensioni di un elemento, dei suoi elementi fratelli o dei suoi elementi precedenti.

Ecco perché ResizeObserver è un'operazione di base utile. Reagisce alle variazioni di dimensione di uno degli elementi osservati, indipendentemente da cosa ha causato la variazione. Fornisce anche l'accesso alle nuove dimensioni degli elementi osservati.

Supporto dei browser

  • Chrome: 64.
  • Edge: 79.
  • Firefox: 69.
  • Safari: 13.1.

Origine

API

Tutte le API con il suffisso Observer che abbiamo menzionato sopra condividono un design semplice. ResizeObserver non fa eccezione. Creando un oggetto ResizeObserver, puoi passare un callback al costruttore. Al callback viene passato un array di oggetti ResizeObserverEntry, una voce per ogni elemento osservato, che contiene le nuove dimensioni dell'elemento.

var ro = new ResizeObserver(entries => {
  for (let entry of entries) {
    const cr = entry.contentRect;

    console.log('Element:', entry.target);
    console.log(`Element size: ${cr.width}px x ${cr.height}px`);
    console.log(`Element padding: ${cr.top}px ; ${cr.left}px`);
  }
});

// Observe one or multiple elements
ro.observe(someElement);

Alcuni dettagli

Che cosa viene registrato?

In genere, un ResizeObserverEntry registra la casella dei contenuti di un elemento tramite una proprietà chiamata contentRect, che restituisce un DOMRectReadOnly oggetto. La casella dei contenuti è la casella in cui è possibile inserire i contenuti. Si tratta della casella di bordo meno la spaziatura interna.

Un diagramma del modello a casella CSS.

È importante notare che, sebbene ResizeObserver registri sia le dimensioni del contentRect sia il padding, monitora solo il contentRect. Non confondere contentRect con il riquadro di delimitazione dell'elemento. La casella delimitante, come segnalata da getBoundingClientRect(), è la casella che contiene l'intero elemento e i relativi discendenti. Gli SVG sono un'eccezione alla regola, in cui ResizeObserver segnalerà le dimensioni del riquadro di delimitazione.

A partire da Chrome 84, ResizeObserverEntry ha tre nuove proprietà per fornire informazioni più dettagliate. Ognuna di queste proprietà restituisce un oggetto ResizeObserverSize contenente una proprietà blockSize e una proprietà inlineSize. Queste informazioni riguardano l'elemento osservato al momento dell'invocazione del callback.

  • borderBoxSize
  • contentBoxSize
  • devicePixelContentBoxSize

Tutti questi elementi restituiscono array di sola lettura perché in futuro si spera che supportino elementi con più frammenti, che si verificano in scenari a più colonne. Per ora, questi array contengono un solo elemento.

Il supporto della piattaforma per queste proprietà è limitato, ma Firefox supporta già le prime due.

Quando viene segnalato?

La specifica prescrive che ResizeObserver debba elaborare tutti gli eventi di ridimensionamento prima di paint e dopo il layout. Di conseguenza, il callback di un ResizeObserver è il luogo ideale per apportare modifiche al layout della pagina. Poiché l'elaborazione di ResizeObserver avviene tra il layout e la pittura, questa operazione invaliderà solo il layout, non la pittura.

Ecco

Potresti chiederti: cosa succede se modifico le dimensioni di un elemento osservato all'interno del callback a ResizeObserver? La risposta è: attiverai subito un'altra chiamata al callback. Fortunatamente, ResizeObserver ha un meccanismo per evitare loop di callback infiniti e dipendenze cicliche. Le modifiche verranno elaborate nello stesso frame solo se l'elemento di cui è stato modificato il ridimensionamento si trova più in profondità nella struttura ad albero DOM rispetto all'elemento più superficiale elaborato nel callback precedente. In caso contrario, verranno differiti al frame successivo.

Applicazione

ResizeObserver ti consente di implementare query sui media per elemento. Osservando gli elementi, puoi definire in modo imperativo i breakpoint del design e modificare gli stili di un elemento. Nel seguente esempio, il secondo riquadro modificherà il raggio del bordo in base alla larghezza.

const ro = new ResizeObserver(entries => {
  for (let entry of entries) {
    entry.target.style.borderRadius =
        Math.max(0, 250 - entry.contentRect.width) + 'px';
  }
});
// Only observe the second box
ro.observe(document.querySelector('.box:nth-child(2)'));

Un altro esempio interessante è una finestra di chat. Il problema che si verifica in un tipico layout di conversazione dall'alto verso il basso è il posizionamento dello scorrimento. Per evitare di confondere l'utente, è utile che la finestra rimanga incollata alla parte inferiore della conversazione, dove vengono visualizzati i messaggi più recenti. Inoltre, qualsiasi tipo di variazione del layout (ad esempio il passaggio da un telefono in orizzontale a uno in verticale o viceversa) dovrebbe ottenere lo stesso risultato.

ResizeObserver ti consente di scrivere un unico frammento di codice che gestisce entrambi gli scenari. Il ridimensionamento della finestra è un evento che un ResizeObserver può acquisire per definizione, ma richiamando appendChild() anche l'elemento viene ridimensionato (a meno che non sia impostato overflow: hidden), perché deve liberare spazio per i nuovi elementi. Tenendo presente questo, sono necessarie pochissime righe per ottenere l'effetto desiderato:

const ro = new ResizeObserver(entries => {
  document.scrollingElement.scrollTop =
    document.scrollingElement.scrollHeight;
});

// Observe the scrollingElement for when the window gets resized
ro.observe(document.scrollingElement);

// Observe the timeline to process new messages
ro.observe(timeline);

Non male, eh?

Da qui, potrei aggiungere altro codice per gestire il caso in cui l'utente abbia scorri manualmente verso l'alto e voglia che lo scorrimento rimanga su quel messaggio quando arriva un nuovo messaggio.

Un altro caso d'uso è per qualsiasi tipo di elemento personalizzato che gestisce il proprio layout. Fino a ResizeObserver, non esisteva un modo affidabile per ricevere una notifica quando le sue dimensioni cambiavano in modo che i relativi elementi secondari potessero essere disposti di nuovo.

Effetti sull’interazione con Next Paint (INP)

Interazione con Next Paint (INP) è una metrica che misura la reattività complessiva di una pagina alle interazioni degli utenti. Se l'INP di una pagina rientra nella soglia "buona", ovvero 200 meno millisecondi, si può dire che la pagina risponde in modo affidabile alle interazioni dell'utente.

Sebbene il tempo necessario per l'esecuzione dei callback degli eventi in risposta a un'interazione utente possa contribuire in modo significativo alla latenza totale di un'interazione, non è l'unico aspetto dell'INP da considerare. L'INP prende in considerazione anche il tempo necessario per la visualizzazione della prossima immagine dell'interazione. ovvero il tempo necessario per completare l'operazione di rendering necessaria per aggiornare l'interfaccia utente in risposta a un'interazione.

Per quanto riguarda ResizeObserver, questo è importante perché il callback che viene eseguita un'istanza ResizerObserver avviene poco prima del rendering. Questo accade per progettazione, in quanto il lavoro che si verifica nel callback deve essere preso in considerazione, poiché il risultato di questo lavoro molto probabilmente richiederà una modifica all'interfaccia utente.

Fai in modo di eseguire il minor lavoro di rendering necessario in un ResizeObserver callback, poiché un lavoro di rendering eccessivo può creare situazioni in cui il browser ha ritardi nell'eseguire operazioni importanti. Ad esempio, se un'interazione ha un callback che ne attiva un altro, assicurati di svolgere quanto segue per facilitare l'esperienza più fluida possibile:

  • Assicurati che i selettori CSS siano il più semplici possibile per evitare un lavoro eccessivo di ricalcolo degli stili. I ricomputi degli stili avvengono appena prima del layout e i selettori CSS complessi possono ritardare le operazioni di layout.
  • Evita di eseguire nel callback ResizeObserver operazioni che possano attivare le ripetizioni forzate.
  • Il tempo necessario per aggiornare il layout di una pagina aumenta in genere con il numero di elementi DOM in una pagina. Anche se questo vale indipendentemente dal fatto che le pagine utilizzino o meno ResizeObserver, il lavoro svolto in un callback ResizeObserver può diventare significativo con l'aumento della complessità strutturale di una pagina.

Conclusione

ResizeObserver è disponibile in tutti i principali browser e offre un modo efficiente per monitorare il ridimensionamento di un elemento a livello di elemento. Fai attenzione a non ritardare troppo il rendering con questa potente API.