Verso una metrica di fluidità dell'animazione

Scopri come misurare le animazioni, come considerare i frame delle animazioni e la fluidità generale della pagina.

Behdad Bakhshinategh
Behdad Bakhshinategh
Jonathan Ross
Jonathan Ross
Michal Mocny
Michal Mocny

Probabilmente hai riscontrato pagine che "balbettano" o "si bloccano" durante lo scorrimento o le animazioni. Ci piace dire che queste esperienze non sono fluide. Per risolvere questi tipi di problemi, il team di Chrome sta lavorando per supportare ulteriormente gli strumenti del nostro lab per il rilevamento delle animazioni, nonché per apportare miglioramenti costanti alla diagnostica della pipeline di rendering in Chromium.

Vorremmo condividere alcuni progressi recenti, offrire indicazioni concrete sugli strumenti e discutere di idee per le future metriche relative alla fluidità dell'animazione. Come sempre, ci farebbe piacere ricevere il tuo feedback.

Questo post tratterà tre argomenti principali:

  • Una rapida occhiata alle animazioni e ai frame di animazione.
  • La nostra posizione attuale in merito alla misurazione della fluidità complessiva dell'animazione.
  • Ecco alcuni suggerimenti pratici che puoi utilizzare oggi negli strumenti di laboratorio.

Che cosa sono le animazioni?

Le animazioni danno vita ai contenuti Spostando i contenuti, soprattutto in risposta alle interazioni degli utenti, le animazioni possono rendere l'esperienza più naturale, comprensibile e divertente.

Tuttavia, animazioni implementate male o l'aggiunta di troppe animazioni possono peggiorare l'esperienza e renderla decisamente poco divertente. Probabilmente tutti abbiamo interagito con un'interfaccia in cui sono stati aggiunti troppi effetti di transizione "utili", che in realtà diventano ostili quando hanno prestazioni scarse. Pertanto, alcuni utenti potrebbero effettivamente preferire la riduzione del movimento, una preferenza dell'utente che dovresti rispettare.

Come funzionano le animazioni?

Per riassumere, la pipeline di rendering è costituita da alcune fasi sequenziali:

  1. Stile: calcola gli stili applicati agli elementi.
  2. Layout: genera la geometria e la posizione di ogni elemento.
  3. Vernice: compila i pixel per ogni elemento nei livelli.
  4. Composto: disegna i livelli sullo schermo.

Sebbene esistano molti modi per definire le animazioni, tutti funzionano fondamentalmente tramite uno dei seguenti metodi:

  • Modifica delle proprietà di layout.
  • Modifica delle proprietà paint.
  • Modifica delle proprietà composite.

Poiché queste fasi sono sequenziali, è importante definire le animazioni in termini di proprietà più avanti nella pipeline. Quanto più l'aggiornamento avviene all'inizio del processo, maggiori sono i costi e minore è la probabilità che proceda senza problemi. Per maggiori dettagli, consulta la sezione Rendimento del rendering.

Sebbene possa essere pratico animare le proprietà di layout, questa operazione comporta dei costi, anche se non sono immediatamente evidenti. Le animazioni devono essere definite in termini di modifiche delle proprietà composite, se possibile.

Definire animazioni CSS dichiarative o utilizzare Web Animation e assicurarti di animare le proprietà composite è un ottimo primo passo per garantire animazioni fluide ed efficienti. Tuttavia, questo da solo non garantisce la fluidità, perché anche le animazioni web efficienti presentano limiti di prestazioni. Ecco perché è sempre importante misurare.

Che cosa sono i frame di animazione?

Gli aggiornamenti alla rappresentazione visiva di una pagina richiedono tempo per essere visualizzati. Una variazione visiva porterà a un nuovo frame dell'animazione, che verrà visualizzato sul display dell'utente.

I display si aggiornano a intervalli regolari, quindi gli aggiornamenti visivi vengono raggruppati. Molti display si aggiornano a un intervallo di tempo fisso, ad esempio 60 volte al secondo (ovvero 60 Hz). Alcuni display più moderni possono offrire frequenze di aggiornamento più elevate (90-120 Hz stanno diventando comuni). Spesso questi display possono adattarsi attivamente tra le frequenze di aggiornamento in base alle esigenze o addirittura offrire frequenze frame completamente variabili.

L'obiettivo di qualsiasi applicazione, come un gioco o un browser, è elaborare tutti questi aggiornamenti visivi raggruppati e produrre un frame di animazione visivamente completo entro la scadenza, ogni volta. Tieni presente che questo obiettivo è completamente distinto da altre importanti attività del browser, come il caricamento rapido dei contenuti dalla rete o l'esecuzione efficiente delle attività JavaScript.

A un certo punto può diventare troppo difficile completare tutti gli aggiornamenti visivi entro la scadenza assegnata assegnata dal display. In questo caso, il browser rimuove un frame. Lo schermo non diventa nero, si limita a ripetersi. Vedrai lo stesso aggiornamento visivo per un po' di tempo in più, ovvero lo stesso frame dell'animazione che è stato presentato nell'occasione del frame precedente.

In realtà, capita spesso. Non è necessariamente neanche percettibile, soprattutto per i contenuti statici o simili a documenti, cosa comune in particolare sulla piattaforma web. I frame eliminati diventano evidenti solo in caso di importanti aggiornamenti visivi, come le animazioni, per i quali è necessario un flusso costante di aggiornamenti dell'animazione per mostrare movimenti fluidi.

Che cosa influisce sui fotogrammi di animazione?

Gli sviluppatori web possono influire notevolmente sulla capacità di un browser di eseguire il rendering e presentare aggiornamenti visivi in modo rapido ed efficiente.

Ecco alcuni esempi:

  • Utilizzo di contenuti troppo grandi o che richiedono molte risorse per essere decodificati rapidamente sul dispositivo di destinazione.
  • Utilizzo di troppi livelli che richiedono troppa memoria GPU.
  • Definizione di animazioni web o stili CSS eccessivamente complessi.
  • Utilizzo di antipattern di progettazione che disattivano le ottimizzazioni di rendering rapido.
  • Troppa attività JS sul thread principale, che porta a attività lunghe che bloccano gli aggiornamenti visivi.

Ma come fai a sapere quando un frame di animazione non ha raggiunto la scadenza e ha causato un frame perso?

Un possibile metodo è l'utilizzo del polling, tuttavia presenta diversi svantaggi. requestAnimationFrame(), o "rAF", comunica al browser che vuoi eseguire un'animazione e chiede di avere la possibilità di farlo prima della fase di pittura successiva della pipeline di rendering. Se la funzione di callback non viene chiamata nel momento previsto, significa che non è stato eseguito un disegno e sono stati ignorati uno o più frame. Effettuando il polling e contando la frequenza con cui viene chiamato rAF, puoi calcolare una sorta di metrica "frame al secondo" (FPS).

let frameTimes = [];
function pollFramesPerSecond(now) {
  frameTimes = [...frameTimes.filter(t => t > now - 1000), now];
  requestAnimationFrame(pollFramesPerSecond);
  console.log('Frames per second:', frameTimes.length);
}
requestAnimationFrame(pollFramesPerSecond);

L'utilizzo del polling requestAnimationFrame() non è una buona idea per diversi motivi:

  • Ogni script deve configurare il proprio loop di polling.
  • Può bloccare il percorso critico.
  • Anche se il polling rAF è veloce, può impedire a requestIdleCallback() di pianificare blocchi di inattività lunghi se utilizzati continuamente (blocchi che superano un singolo frame).
  • Analogamente, la mancanza di blocchi inattivi lunghi impedisce al browser di pianificare altre attività che richiedono molto tempo (come la raccolta dei rifiuti più lunga e altri lavori in background o speculativi).
  • Se il polling viene attivato e disattivato, perderai i casi in cui il budget del frame è stato superato.
  • Il polling segnalerà falsi positivi nei casi in cui il browser utilizzi una frequenza di aggiornamento variabile (ad esempio a causa dello stato di alimentazione o visibilità).
  • E, cosa più importante, non acquisisce tutti i tipi di aggiornamenti delle animazioni.

Un carico di lavoro eccessivo sul thread principale può influire sulla possibilità di vedere i frame dell'animazione. Dai un'occhiata al Sample di jank per scoprire in che modo un'animazione basata su rAF, quando c'è troppo lavoro sul thread principale (ad esempio il layout), porta alla perdita di frame e a un minor numero di callback rAF, nonché a un calo degli FPS.

Quando il thread principale si blocca, gli aggiornamenti visivi iniziano a essere discontinui. È una schifezza.

Molti strumenti di misurazione si sono concentrati molto sulla capacità del thread principale di generare in modo tempestivo e sulla fluidità dei frame di animazione. Ma non è tutto. Considera l'esempio seguente:

Il video riportato sopra mostra una pagina che inserisce periodicamente attività lunghe nel thread principale. Queste lunghe attività rovinano completamente la capacità della pagina di fornire determinati tipi di aggiornamenti visivi e nell'angolo in alto a sinistra puoi vedere un calo corrispondente di requestAnimationFrame() FPS registrati a 0.

Eppure, nonostante queste lunghe attività, la pagina continua a scorrere senza problemi. Questo accade perché nei browser moderni lo scorrimento è spesso in threaded, gestito interamente dal compositore.

Questo è un esempio che contiene contemporaneamente molti frame persi nel thread principale, ma presenta ancora molti frame di scorrimento inviati correttamente nel thread del compositore. Una volta completata l'attività lunga, l'aggiornamento della visualizzazione del thread principale non presenta comunque modifiche da offrire. Il polling rAF ha suggerito un calo del frame a 0, ma visivamente l'utente non potrebbe notare alcuna differenza.

Per i frame di animazione, la situazione non è così semplice.

Fotogrammi di animazione: aggiornamenti importanti

L'esempio riportato sopra mostra che la storia non si limita a requestAnimationFrame().

Quindi, quando sono importanti gli aggiornamenti e i frame di animazione? Ecco alcuni criteri che stiamo prendendo in considerazione e su cui ci piacerebbe ricevere un feedback:

  • Aggiornamenti dei thread principali e di Composer
  • Aggiornamenti relativi alla vernice mancanti
  • Rilevamento di animazioni
  • Qualità e quantità

Aggiornamenti del thread principale e del compositore

Gli aggiornamenti dei fotogrammi dell'animazione non sono booleani. Non è possibile rimuovere completamente i frame o presentarli completamente. Esistono diversi motivi per cui un fotogramma animato può essere parzialmente presentato. In altre parole, può includere contemporaneamente contenuti non aggiornati e nuovi aggiornamenti visivi.

L'esempio più comune è quando il browser non è in grado di produrre un nuovo aggiornamento del thread principale entro la scadenza del frame, ma ha un nuovo aggiornamento del thread del compositore (ad esempio l'esempio di scorrimento threaded di prima).

Un motivo importante per cui è consigliabile utilizzare animazioni dichiarative per animare le proprietà composite è che in questo modo è possibile gestire un'animazione interamente dal thread del compositore anche quando il thread principale è occupato. Questi tipi di animazioni possono continuare a produrre aggiornamenti visivi in modo efficiente e parallelo.

D'altra parte, potrebbero verificarsi casi in cui un aggiornamento del thread principale diventi finalmente disponibile per la presentazione, ma solo dopo aver perso diverse scadenze del frame. In questo caso, il browser avrà alcuni aggiornamenti recenti, ma potrebbe non essere l'ultimo.

In linea di massima, consideriamo frame parziali i frame che contengono alcuni nuovi aggiornamenti visivi, ma non tutti. I frame parziali sono abbastanza comuni. Idealmente, gli aggiornamenti parziali includerebbero almeno gli aggiornamenti visivi più importanti, come le animazioni, ma questo può accadere solo se le animazioni sono guidate dal thread del compositore.

Aggiornamenti di Paint mancanti

Un altro tipo di aggiornamento parziale si verifica quando i contenuti multimediali come le immagini non sono stati decodificati e rasterizzati in tempo per la presentazione del frame.

In alternativa, anche se una pagina è perfettamente statica, i browser potrebbero non riuscire a tenere il passo con il rendering degli aggiornamenti visivi durante lo scorrimento rapido. Questo perché le rappresentazioni dei pixel dei contenuti al di fuori dell'area visibile possono essere ignorate per risparmiare memoria GPU. Il rendering dei pixel richiede tempo e potrebbe essere necessario più di un frame per eseguire il rendering di tutto dopo una scorrimento ampio, ad esempio con un movimento del dito. Questo fenomeno è comunemente conosciuto come quadri a scacchi.

Con ogni opportunità di rendering del frame, è possibile monitorare la quantità di aggiornamenti visivi più recenti che sono effettivamente arrivati sullo schermo. La misurazione della capacità di farlo su molti frame (o nel tempo) è nota in generale come throughput dei frame.

Se la GPU è davvero ingolfata, il browser (o la piattaforma) potrebbe persino iniziare a limitare la frequenza con cui tenta gli aggiornamenti visivi e quindi a diminuire le frequenze frame effettive. Sebbene tecnicamente questo possa ridurre il numero di aggiornamenti dei fotogrammi persi, visivamente verrà comunque visualizzata una maggiore velocità in termini di fotogrammi.

Tuttavia, non tutti i tipi di velocità effettiva frame bassi sono scadenti. Se la pagina è inattiva per la maggior parte del tempo e non sono presenti animazioni attive, una frequenza fotogrammi bassa è visivamente accattivante quanto una frequenza fotogrammi elevata (e può farti risparmiare batteria).

Quindi, quando è importante la frequenza fotogrammi?

Rilevamento di animazioni

Una velocità effettiva frame elevata è importante soprattutto nei periodi con animazioni importanti. I diversi tipi di animazione dipendono dagli aggiornamenti visivi di un thread specifico (principale, compositore o un worker), pertanto l'aggiornamento visivo dipende dal fatto che il thread fornisca l'aggiornamento entro la scadenza. Diciamo che un determinato thread influisce sulla fluidità ogni volta che è presente un'animazione attiva che dipende da quell'aggiornamento del thread.

Alcuni tipi di animazioni sono più facili da definire e rilevare rispetto ad altri. Le animazioni dichiarative, o basate sull'input dell'utente, sono più chiare da definire rispetto alle animazioni basate su JavaScript implementate come aggiornamenti periodici alle proprietà di stile animabili.

Anche con requestAnimationFrame() non puoi sempre assumere che ogni chiamata rAF produca necessariamente un aggiornamento o un'animazione visiva. Ad esempio, l'uso del polling rAF solo per tracciare la frequenza fotogrammi (come mostrato sopra) non dovrebbe influire di per sé sulle misurazioni della fluidità poiché non c'è alcun aggiornamento visivo.

Qualità e quantità

Infine, il rilevamento delle animazioni e degli aggiornamenti dei frame delle animazioni è solo una parte della storia perché acquisisce solo la quantità di aggiornamenti delle animazioni, non la qualità.

Ad esempio, potresti vedere una frequenza fotogrammi costante di 60 fps mentre guardi un video. Tecnicamente, il video è perfettamente fluido, ma potrebbe avere una bassa velocità in bit o problemi di buffering della rete. Ciò non viene acquisito direttamente dalle metriche di fluidità dell'animazione, ma potrebbe essere comunque fastidioso per l'utente.

In alternativa, un gioco che sfrutta <canvas> (magari anche utilizzando tecniche come il canvas offscreen per assicurarsi una frequenza frame costante) potrebbe tecnicamente essere perfettamente scorrevole in termini di fotogrammi di animazione, pur non riuscendo a caricare asset di gioco di alta qualità nella scena o mostrando artefatti di rendering.

Ovviamente, un sito può avere solo animazioni di pessima qualità 🙂

GIF di una vecchia scuola in costruzione

Voglio dire, erano piuttosto cool per l'epoca.

Stati di un singolo frame dell'animazione

Poiché i fotogrammi possono essere presentati parzialmente o possono verificarsi perdite di fotogrammi in modi che non influiscono sulla fluidità, abbiamo iniziato a considerare ogni fotogramma come dotato di un punteggio di completezza o fluidità.

Ecco lo spettro di modi in cui interpretiamo lo stato di un singolo fotogramma dell'animazione, dal caso migliore al caso peggiore:

Nessun aggiornamento richiesto Tempo di inattività, ripetizione del frame precedente.
Completamente presentata L'aggiornamento del thread principale è stato eseguito entro la scadenza oppure non era richiesto alcun aggiornamento del thread principale.
Presentazione parziale Solo compositore; l'aggiornamento del thread principale in ritardo non ha comportato alcuna variazione visiva.
Presentazione parziale Solo per il compositore; il thread principale ha ricevuto un aggiornamento visivo, ma questo aggiornamento non includeva un'animazione che influisce sulla fluidità.
Presentazione parziale Solo compositore. Il thread principale aveva un aggiornamento visivo che influisce sulla fluidità, ma è stato utilizzato un frame precedentemente inattivo.
Presentazione parziale Solo compositore; senza l'aggiornamento principale desiderato e l'aggiornamento del compositore ha un'animazione che influisce sulla fluidità.
Presentazione parziale Solo il compositore, ma l'aggiornamento del compositore non ha un'animazione che influisce sulla fluidità.
Frame perso Nessun aggiornamento. Non è stato richiesto alcun aggiornamento del compositore e il comando main ha subito un ritardo.
Frame interrotto Volevamo un aggiornamento del compositore, ma è stato ritardato.
Frame non aggiornato Era richiesto un aggiornamento, che è stato prodotto dal renderer, ma la GPU non lo ha ancora presentato prima della scadenza vsync.

È possibile trasformare questi stati in una sorta di punteggio. Un modo per interpretare questo punteggio è considerarlo una probabilità di essere osservabile dall'utente. Un singolo frame eliminato potrebbe non essere molto osservabile, ma sicuramente una sequenza di molti frame eliminati che influisce sull'uniformità in una riga.

Riepilogo: una metrica Percentuale frame persi

Anche se a volte può essere necessario esaminare in dettaglio lo stato di ogni frame dell'animazione, è utile anche assegnare un rapido punteggio "a colpo d'occhio" per un'esperienza.

Poiché i frame possono essere presentati parzialmente e poiché anche gli aggiornamenti dei frame completamente saltati potrebbero non influire sulla fluidità, vogliamo concentrarci meno sul semplice conteggio dei frame e più sull'entità in cui il browser non è in grado di fornire aggiornamenti visivamente completi quando è importante.

Il modello mentale deve passare da:

  1. Frame al secondo, a
  2. Rilevamento di aggiornamenti mancanti e importanti delle animazioni, per
  3. Percentuale diminuita in un determinato periodo di tempo.

Ciò che conta è: la proporzione di tempo di attesa di aggiornamenti importanti. Riteniamo che questo corrisponda al modo naturale in cui gli utenti sperimentano la fluidità degli elementi web nella pratica. Finora abbiamo utilizzato quanto segue come insieme iniziale di metriche:

  • Percentuale media di frame persi: per tutti i fotogrammi di animazione non inattivi nell'intera sequenza temporale
  • Caso peggiore di percentuale di frame persi: misurata su finestre di tempo scorrevoli di 1 secondo.
  • 95° percentile della percentuale di frame persi: misurato su finestre di tempo scorrevoli di 1 secondo.

Puoi trovare questi punteggi in alcuni strumenti per sviluppatori di Chrome oggi. Sebbene queste metriche si concentrino solo sul throughput complessivo dei frame, valutiamo anche altri fattori, come la latenza dei frame.

Prova tu stesso gli strumenti per sviluppatori.

HUD sul rendimento

Chromium ha un'elegante interfaccia HUD per le prestazioni nascosta dietro un flag (chrome://flags/#show-performance-metrics-hud). Al suo interno puoi trovare i risultati in tempo reale per elementi come i Core Web Vitals, nonché alcune definizioni sperimentali per la fluidità dell'animazione in base alla percentuale di frame persi nel tempo.

HUD sul rendimento

Statistiche di rendering del frame

Abilita "Statistiche di rendering dei frame" in DevTools tramite le impostazioni di rendering per avere una visualizzazione in tempo reale dei nuovi frame di animazione, che sono codificati per colore per distinguere gli aggiornamenti parziali da quelli dei frame completamente eliminati. I fotogrammi al secondo indicati si riferiscono solo ai fotogrammi completamente presentati.

Statistiche di rendering del frame

Visualizzatore frame nelle registrazioni del profilo delle prestazioni di DevTools

Il pannello Rendimento di DevTools include da tempo un visualizzatore di frame. Tuttavia, non era più in linea con il funzionamento effettivo della pipeline di rendering moderna. Di recente sono stati apportati molti miglioramenti, anche nell'ultima versione di Chrome Canary, che riteniamo semplificheranno notevolmente il debug dei problemi di animazione.

Oggi scoprirai che i frame nel visualizzatore frame sono più allineati con i limiti di Vsync e sono codificati per colore in base allo stato. Non è ancora disponibile una visualizzazione completa di tutte le sfumature descritte sopra, ma prevediamo di aggiungerne altre nel prossimo futuro.

Visualizzatore frame in Chrome DevTools

Monitoraggio di Chrome

Infine, con Chrome Tracing, lo strumento di scelta per esaminare in dettaglio i problemi, puoi registrare una traccia "Rendering dei contenuti web" tramite la nuova interfaccia utente di Perfetto (o about:tracing) e analizzare in dettaglio la pipeline di grafica di Chrome. Può essere un'impresa ardua, ma di recente sono state aggiunte alcune funzionalità a Chromium per semplificare la procedura. È possibile ottenere una panoramica di ciò che è disponibile nel documento La vita di un frame.

Attraverso gli eventi traccia, puoi determinare in modo definitivo:

  • Quali animazioni sono in esecuzione (utilizzando eventi denominati TrackerValidation).
  • Ottenere la sequenza temporale esatta dei frame di animazione (utilizzando eventi denominati PipelineReporter).
  • Per aggiornamenti complessi dell'animazione, scopri esattamente cosa blocca l'esecuzione più rapida dell'animazione (utilizzando la suddivisione degli eventi negli eventi PipelineReporter).
  • Per le animazioni basate sugli input, controlla il tempo necessario per ottenere un aggiornamento visivo (utilizzando gli eventi denominati EventLatency).

Report sulla pipeline di Chrome Tracing

Passaggi successivi

L'iniziativa Web Vitals ha lo scopo di fornire metriche e indicazioni per creare ottime esperienze utente sul web. Le metriche basate su dati di laboratorio, come il tempo di blocco totale (TBT), sono fondamentali per rilevare e diagnosticare potenziali problemi di interattività. Abbiamo in programma di progettare una metrica simile basata su lab per la fluidità dell'animazione.

Ti aggiorneremo man mano che continuiamo a elaborare idee per progettare una metrica completa basata sui dati dei singoli frame di animazione.

In futuro, vorremmo anche progettare API che consentano di misurare la fluidità dell'animazione in modo efficiente per gli utenti reali sul campo e in laboratorio. Continua a seguirci per non perderti gli aggiornamenti.

Feedback

Siamo entusiasti di tutti i recenti miglioramenti e degli strumenti per sviluppatori che sono stati forniti in Chrome per misurare la fluidità dell'animazione. Prova questi strumenti, effettua il benchmarking delle animazioni e facci sapere cosa ne pensi.

Puoi inviare i tuoi commenti al gruppo Google web-vitals-feedback con "[Metriche fluidità]" nella riga dell'oggetto. Non vediamo l'ora di conoscere la tua opinione.