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 si è impegnato ad aggiungere ulteriore supporto ai nostri strumenti di laboratorio per il rilevamento delle animazioni, nonché a apportare continui miglioramenti 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 fotogrammi 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. Se i contenuti si muovono, soprattutto in risposta alle interazioni degli utenti, le animazioni possono rendere un'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 che ha aggiunto troppi effetti di transizione "utili", che diventano in realtà ostili all'esperienza quando hanno un rendimento scadente. 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, tutte funzionano fondamentalmente tramite uno dei seguenti elementi:

  • 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, solo questo non garantisce la fluidità perché anche le animazioni web efficienti hanno 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.

Mostra l'aggiornamento a un determinato intervallo, 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 dal display. In questo caso, il browser rimuove un frame. Lo schermo non diventa nero, ma si ripete. 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 nemmeno percepibile, soprattutto per i contenuti statici o simili a documenti, che sono comuni in particolare sulla piattaforma web. I frame persi diventano evidenti solo quando ci sono aggiornamenti visivi importanti, come le animazioni, per le quali abbiamo bisogno di un flusso costante di aggiornamenti per mostrare un movimento fluido.

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 ad 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 requestAnimationFrame() polling, che però 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 requestIdleCallback() di pianificare blocchi inattivi lunghi se utilizzato 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 e a un FPS inferiore.

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 qui 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 riferimento 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. Al termine dell'attività lunga, l'aggiornamento della pittura del thread principale non offre comunque alcuna modifica visiva. Il polling rAF ha suggerito un calo del frame a 0, ma visivamente, un utente non sarebbe in grado di notare una 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 delle animazioni? Ecco alcuni criteri che stiamo prendendo in considerazione e su cui ci piacerebbe ricevere un feedback:

  • Aggiornamenti dei thread principali e di Composer
  • Aggiornamenti di Paint mancanti
  • Rilevamento di animazioni
  • Qualità rispetto alla quantità

Aggiornamenti dei thread principali e di Composer

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 dell'animazione potrebbe 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 quadruccio.

Con ogni opportunità di rendering del frame, è possibile monitorare la quantità di aggiornamenti visivi più recenti che sono stati effettivamente visualizzati 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 throughput a frame basso sono negativi. 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

La velocità in termini di frame è importante soprattutto durante i 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 dall'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'utilizzo del polling rAF solo per monitorare la frequenza frame (come mostrato sopra) non dovrebbe influire sulle misurazioni della fluidità poiché non c'è nessun aggiornamento visivo.

Qualità rispetto alla 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. Questo aspetto non viene rilevato direttamente dalle metriche relative alla fluidità dell'animazione, ma può comunque essere spiacevole 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.

E, naturalmente, un sito può avere animazioni davvero pessime 🙂

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 dei 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 o non è stato 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 ha subito un aggiornamento visivo che influisce sulla scorrevolezza, ma è stato utilizzato un frame precedente non aggiornato.
Presentazione parziale Solo compositore; senza l'aggiornamento principale desiderato e l'aggiornamento del compositore ha un'animazione che influisce sulla fluidità.
Presentazione parziale Solo 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 programma principale è stato ritardato.
Frame perso Era previsto un aggiornamento del compositore, ma è stato ritardato.
Frame non aggiornato Era stato 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. E forse un modo per interpretare questo punteggio è considerarlo una probabilità di essere osservabile dall'utente. Un singolo frame perso potrebbe non essere molto evidente, ma una sequenza di molti frame persi che influiscono sulla fluidità in una riga lo è sicuramente.

Riepilogo: una metrica Percentuale frame persi

Anche se a volte può essere necessario esaminare in dettaglio lo stato di ogni keyframe dell'animazione, è utile anche assegnare un punteggio rapido "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. Rilevare aggiornamenti mancanti e importanti delle animazioni, per
  3. Percentuale di utenti persi in un determinato periodo di tempo.

Ciò che conta è la proporzione di tempo di attesa per 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.

Oggi puoi trovare questi punteggi in alcuni strumenti per sviluppatori di Chrome. 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 gli sviluppatori.

HUD sul rendimento

Chromium ha un'utile interfaccia HUD per le prestazioni nascosta dietro un flag (chrome://flags/#show-performance-metrics-hud). Qui 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

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

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 noterai che i frame nel visualizzatore dei frame sono meglio allineati ai confini vsync e codificati in base allo stato. Non è ancora disponibile una visualizzazione completa di tutte le sfumature descritte sopra, ma abbiamo in programma di aggiungerne altre nel prossimo futuro.

Visualizzatore di 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. Puoi avere una panoramica di ciò che è disponibile nel documento Vita di un frame.

Tramite gli eventi traccia puoi determinare definitivamente:

  • Quali animazioni sono in esecuzione (utilizzando eventi denominati TrackerValidation).
  • Ottenere la sequenza temporale esatta dei frame di animazione (utilizzando eventi denominati PipelineReporter).
  • Per gli aggiornamenti dell'animazione a scatti, scopri esattamente cosa impedisce all'animazione di funzionare più velocemente (utilizzando le suddivisioni degli eventi all'interno degli 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 miglioramenti e gli strumenti per sviluppatori recenti che sono stati integrati in Chrome per misurare la fluidità delle animazioni. Prova questi strumenti, esegui 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.