In questo approfondimento andremo a discutere di come ottimizzare al meglio le animazioni grazie all’uso di CSS, sarà necessario un approfondimento teorico per capire alcune dinamiche che avvengono dietro le quinte.
In questo modo le ottimizzazioni che andremo a fare saranno più consapevoli.
Il frame rate alla quale puntare
Quando si tratta di animare sul web il frame rate alla quale puntare è di 60 FPS. Questo valore serve a visualizzare un'animazione fluida. Nel web un frame è il tempo impiegato per aggiornare e ridipingere lo schermo.
Se ogni frame non si completa in 16.7ms, ci sono alte possibilità che l’utente percepisca un ritardo.
Se ti stai chiedendo da dove sia saltato quel 16.7ms è il risultato della divisione di 1000ms in 60 frame.
La rendering pipeline
Per renderizzare qualcosa in pagina il browser segue questi passaggi:
- Style: calcola gli stili da applicare agli elementi
- Layout: genera la geometria e la posizione di ogni elemento
- Paint: riempie i pixel di ogni elemento in diversi layer
- Composition: disegna i layer a schermo
Questi quattro step sono noti come rendering pipeline.
Il browser non esegue mai questo flusso da un punto non meglio definito, ma ad ogni animazione la pipeline viene ripercorsa dal punto 1 al 4. Ogni volta.
Nel momento in cui sto scrivendo queste righe non ho ancora una lezione specifica sul come il browser gestisce questo aspetto, ma sono abbastanza sicuro che arriverà presto, quindi per ora se vuoi approfondire meglio il come il browser gestisce questi aspetti, ti consiglio la lettura di questo meraviglioso articolo di A List Apart su come il browser gestisce CSS
Animazione delle proprietà del layout
Quando avviene un cambiamento in un layer devono essere svolti dei calcoli per capire l’entità dei cambiamenti.
La modifica, infatti, potrebbe inficiare su altri elementi per i motivi più disparati (es. la posizione dipende dalla larghezza dell’elemento animato, l’overflow, ecc.)
Più è grande il layer, più lo sforzo computazionale è alto.
Animazione delle proprietà del paint
Paint è il processo che determina in quale ordine gli elementi dovrebbero essere dipinti a schermo. Solitamente questa è il compito più impegnativo.
La maggior parte del processo di painting nei browser moderni viene eseguita nei software rasterizer.
In base ai layer in cui gli elementi sono raggruppati, altri elementi potrebbero avere la necessità di essere ridipinti.
I software rasterizer
Piccola parentesi su questi tipi di software i quali generano una rasterizzazione della pagina web, se hai seguito il corso di HTML avrai sicuramente visto la lezione sul come il browser interpreta HTML.
Quando il documento HTML viene parsificato e viene generato il DOM, il passo successivo è capire cosa deve essere mostrato e dove. Queste informazioni sono disponibili nei layer trees, i quali non sono altro che una versione semplificata del DOM, la quale contiene solo gli elementi visibili nella pagina, i quali a loro volta possono essere raggruppati in singoli livelli.
Per una questione di performance la pagina viene suddivisa in griglie di dimensioni variabili (possono anche accavallarsi a volte).
Esempio di bordi dei vari layer.
Rasterizzare una singola area è più efficiente che rasterizzare l’intero sito. Se l’utente cliccasse su un animazione che si verifica all’interno di una singola area, solo quell’area verrebbe modificata e non tutta la pagina web.
Per visualizzare i bordi dei layer bisogna, in Chrome Dev Tools, eseguire la command palette (Ctrl
+ Shift
+ P
) e scrivere “Show Rendering” nel pannello che si aprirà flaggare la voce “Layer border”.
Le due modalità di rasterizzazione sono:
- software rasterization: la CPU invia l’area da rasterizzare alla GPU come texture
- GPU rasterization: la rasterizzazione usa direttamente la GPU con OpenGL
Per quanto la GPU rasterization sia piú recente, non è il rimpiazzo della software rasterization, ognuna delle modalità ha i suoi vantaggi e svantaggi, i quali vengono spiegati molto bene in questo approfondimento di Intel su la rasterizzazione in chromium.
Animazione delle proprietà del composite
Il compositing è quel processo che separa la pagina in layer, convertendo le informazioni in pixel (rasterizzazione) e assemblando i layer insieme per creare la pagina (compositing).
Cos’è un layer?
Il termine layer è stato ripetuto più e più volte durante i paragrafi precedenti, ma effettivamente a cosa fa riferimento?
Se avete familiarità con Photoshop e software affini, il concetto di layer (livello) non vi è nuovo. Il concetto di fondo è che un livello contiene una serie di elementi che possono essere mossi insieme.
Allo stesso modo il browser crea in autonomia questi layer per dividere gli elementi che verranno animati così che in caso di repainting non sia necessario eseguire l’operazione su tutta la pagina web.
Ogni layer occupa memoria, quindi forzarne la creazione è un’attività da fare con estrema cautela.
Su dispositivi di fascia media potremmo creare più problemi e dobbiamo tenere conto che ogni texture dei layer viene caricata nella GPU. Portando ad ulteriori colli di bottiglia dovuti alla larghezza di banda tra CPU e la stessa GPU.
Le animazioni sono più performanti con JavaScript o CSS?
Se arrivati a questo punto ti stai facendo questa domanda è più che normale, tantissime librerie usano JavaScript per poter fare animazioni complesse, ma non tutti i tipi di animazioni vanno bene per JS.
Devi sapere infatti che JavaScript per la sua esecuzione usa il main thread, mentre alcune animazioni basate su CSS usano un thread noto come compositor thread.
Sono due thread differenti, ciò significa che se il main thread fosse impegnato, le animazioni non verranno interrotte.
Se però un'animazione innesca un repainting, questo processo viene gestito dal main thread e di conseguenza si avrebbero dei rallentamenti notevoli in pagina.
Come si creano animazioni performanti?
Ci troviamo davanti alla domanda da un milione di dollari. Quando si parla di animazioni i browser moderni hanno due proprietà che permettono di animare a “basso costo”: transform
e opacity
.
La peculiarità di queste due proprietà è che non fanno scattare il repainting.
Il browser determina in autonomia cosa inserire nel layer che verrà animato, ma volendo possiamo forzare la creazione di un layer attraverso la proprietà will-change
.
Creare nuovi layer con will-change
will-change
permette di creare un nuovo layer dal momento che suggeriamo al browser che l’elemento sarà oggetto di modifiche.
Attenzione però:
- La creazione di nuovi layer comporta l’impiego di maggior memoria
will-change
è da considerare come soluzione finale se stiamo cercando di migliorare qualcosa di esistente, ma da evitare se stiamo cercando di ottimizzare preventivamente
Una piccola curiosità su will-change
che magari ai frontend developer più datati può essere utile è che “sostituisce” un vecchio hack che veniva fatto in passato per comunicare al browser che volevamo usare la GPU del dispositivo per rendere le animazioni più fluide.
In sostanza si imbrogliava il browser facendogli inviare le animazioni alla GPU, al posto di farle gestire alla CPU.
1transform: translate3d(0, 0, 0);
Forzare però la creazione dei layer comporta uno spreco di RAM e di GPU e considerando che non tutti i dispositivi (soprattutto mobile) non sono di fascia alta, ci porta ad ottenere l’effetto contrario.
Per evitare il trucchetto e quindi la forzatura di questo passaggio alla GPU è stata introdotta l’appena vista proprietà will-change
.
Se vuoi approfondire consiglio la lettura dell’approfondimento di Opera in merito alla proprietà will-change.
Creiamo un'animazione non performante
Per capire al meglio l’ottimizzazione di un'animazione bisogna innanzitutto capire dove sta il problema in origine.
Creiamo dunque un’animazione apparentemente innocua.
N.b. Il codice che andremo a scrivere è stato pubblicato originariamente in web.dev
1<!DOCTYPE html>
2<html lang="it-IT">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Animazione non performante</title>
7 <link rel="stylesheet" href="style.css">
8</head>
9<body>
10 <div class="container">
11 <div class="box"></div>
12 </div>
13</body>
14</html>
Non c’è granché da commentare qui, se non ti fosse chiaro questo che è stato scritto forse allora è il caso di seguire il mio corso di HTML gratuito.
Il foglio di stile invece è questo:
1*, *::before, *::after {
2 box-sizing: border-box;
3}
4
5body {
6 margin: 5vh 5vw;
7 background: #1a202c;
8}
9
10.container {
11 margin: 0 auto;
12 height: 90vh;
13 background: #65ccb0;
14 background: linear-gradient(90deg, #65ccb0 0%, #cb983c 100%);
15 border-radius: 5px;
16 position: relative;
17}
18
19.box {
20 background-color: #1a202c;
21 width: 100px;
22 height: 100px;
23 position: absolute;
24 top: 10px;
25 left: 10px;
26 border-radius: 5px;
27 animation: move 3s ease infinite;
28}
29
30@keyframes move {
31 50% {
32 top: calc(90vh - 110px);
33 left: calc(90vw - 110px);
34 }
35}
Un piccolo commento:
- 1-3: modifico il calcolo del box-sizing di tutti gli elementi della pagina
- 5-26: stili generali che servono più che altro ad abbellire la demo
- 27 impostazione dell’animazione personalizzata
move
- 30-35:
@keyframes
che anima il box spostandolo dall’angolo in alto a sinistra all’angolo in basso a destra
Come si fa a determinare che l’animazione che abbiamo creato è poco performante? Con Chrome Dev Tools
Analizzare un’animazione con Chrome Dev Tools
A luglio feci una diretta dove ho presentato Chrome Dev Tools e le sue funzionalità, consiglio vivamente la visione se interessati alle varie funzionalità. In questo momento a noi servono solo due strumenti nello specifico: performance e rendering.
Quindi per analizzare la nostra animazione dobbiamo andare nel pannello Performance e cliccare l’icona per registrare l’animazione.
Dopo qualche secondo (5 sono più che sufficienti) interrompere la registrazione e attendere l’elaborazione dei dati.
Dal momento che il valore di Rendering (il quadratino viola) è diverso da zero, significa che c’è qualcosa che causando l’esecuzione dell’intera rendering pipeline.
Per quanto nella nostra demo sia abbastanza semplice comprendere cosa stia causando il rendering, possiamo usare le impostazioni offerte da Rendering per poterlo vedere visivamente.
Sempre in Chrome Dev Tools usa la scorciatoia da tastiera Ctrl + Shift + P (command palette) e scrivere Show Rendering e dal pannello che si apre selezionare:
- Paint flashing: il quale mostra con un colore verde le aree che vengono ridipinte.
- Layer borders: l’abbiamo visto in precedenza e mostra le varie aree in cui la pagina è stata suddivisa
- Frame rendering stats: ci mostra gli FPS della nostra animazione e l’impegno della GPU.
Se avete un computer particolarmente performante i dati saranno simili a quelli dei miei screenshot, nel caso invece possedete un terminale non proprio top di gamma è possibile che il framerate sia più basso, come anche l’impiego della GPU.
Ottimizziamo l’animazione
Cosa ho detto prima delle proprietà a “basso costo” per animare? Sono transform
e opacity
, noi cosa abbiamo usato per animare?
1@keyframes move {
2 50% {
3 top: calc(90vh - 110px);
4 left: calc(90vw - 110px);
5 }
6}
Modifichiamo questo keyframe usando transform
1@keyframes move {
2 50% {
3 transform: translate(calc(90vw - 110px), calc(90vh - 110px));
4 }
5}
Per poter spostare un elemento possiamo usare la funzione translate()
. Qual è il risultato?
Minor impatto sulla GPU
Nessun overlay verde causato dal repainting, perché transform
non fa scattare tutto il processo della pipeline.
Con questo semplice cambiamento siamo riusciti ad ottimizzare l’animazione del nostro quadratino.
Ovvio che l’uso di queste proprietà non si applica solo a CSS, ma se dovessimo fare della animazioni più complesse con JavaScript dobbiamo sempre ricordarci che transform
e opacity
non fanno scattare il repainting, quindi sono decisamente più indicate per poter creare animazioni più performanti e meno impattanti sulle performance generali della pagina.
Per quanto riguarda l’impatto di una buona ottimizzazione lato JavaScript consiglio sempre la diretta di luglio su Chrome Dev Tools dove faccio vedere come analizzare uno script non ottimizzato e l’effetto di una semplice ottimizzazione, quanto può effettivamente impattare sulla visualizzazione delle animazioni.
Ricapitolando
Dove possibile usare opacity
e transform
per evitare il repainting e ottimizzare le animazioni. Tenere sempre Chrome Dev Tools o simile sottomano per ispezionare come si deve ciò che stiamo guardando e capire il margine di ottimizzazione.
Caricamento...
Diventiamo amici di penna? ✒️
Iscriviti alla newsletter per ricevere una mail ogni paio di settimane con le ultime novità, gli ultimi approfondimenti e gli ultimi corsi gratuiti puubblicati. Ogni tanto potrei scrivere qualcosa di commerciale, ma solo se mi autorizzi, altrimenti non ti disturberò oltre.
Se non ti piace la newsletter ti ricordo la community su Discord, dove puoi chiedere aiuto, fare domande e condividere le tue esperienze (ma soprattutto scambiare due meme con me). Ti aspetto!