Scrolling Performance

HTML5 Rocks

Introduzione

In prima istanza lo scrolling non sembra essere qualcosa cui penseresti in termini di performance. Dopotutto, i tuoi contenuti sono in stile e tutti i tuoi asset sono caricati o in fase di caricamento, quindi perchè tutto a un tratto dovrebbe interessarci cosa succede quando scrolliamo? Un motivo molto semplice è che ogni volta che scrolli il browser ha bisogno di disegnare sullo schermo il tuo sito o la tua applicazione. Questo significa che ci è data la possibilità di minimizzare il lavoro che il browser deve eseguire per disegnare tutti gli elementi e, di conseguenza, massimizzare le performance della pagina.

In effetti, disporre di uno scroll fluido è un'aspetto spesso trascurato, sebbene cruciale, dell'esperienza utente nell'uso della tua applicazione. Quando utilizzi uno scrolling molto performante la tua applicazione appare liscia come seta e gradevole da utilizzare, il contrario di un'esperienza d'uso goffa e innaturale.

Le Specifiche Dello Scrolling

Diamo un'occhiata a che cosa succede quando si scrolla. Per capire meglio questo argomento dobbiamo dare una brevissima occhiata anche a come il browser disegna effettivamente gli oggetti sullo schermo. Tutto ha inizio con l'albero DOM, che in pratica contiene tutti gli elementi all'interno della pagina. Il browser osserva il DOM in stile e rintraccia gli oggetti che crede resteranno inalterati quando si scrolla. Il browser poi raggruppa questi elementi e ne scatta una foto, chiamata anche livello. Ognuno di questi livelli ha bisogno di essere impresso e rasterizzato su una texture e quindi composti con l'immagine che tu osservi sullo schermo.

Se sei interessato ad altri dettagli sulla renderizzazione di Chrome leggi questo articolo riassuntivo.

Ogni volta che scrolli una pagina il browser avrà probabilmente bisogno di disegnare alcuni dei pixel in quei livelli (talvolta chiamati livelli compositori). Raggruppando gli oggetti in livelli, quando cambia qualcosa in un livello dobbiamo solo aggiornare la texture di quello specifico livello, e dove possibile possiamo solo disegnare e rasterizzare la porzione di texture di un livello di rendering che è stato danneggiato piuttosto che ridisegnare tutto. Ovviamente se sono presenti oggetti in movimento durante lo scrolling, come in un sito con parallasse, allora stai potenzialmente deteriorando una porzione di pagina molto grande, forse comprendente più livelli, e questo può comportare un lavoro di disegno piuttosto dispendioso.

In breve, meno si disegna meglio è.

Diagnosticare gli Elementi Disegnabili

Dopo tanta teoria, diamo uno sguardo a come tutto questo funziona in pratica. Qui puoi trovare una demo page da osservare mediante i DevTools di Chrome. Se apri il pannello della Timeline, lo imposti in modalità frame, premi il tasto per la registrazione e inizi a scrollare la pagina, dovresti vedere un mucchio di barre verdi. Ho registrato un video in cui mostro cosa fare e cosa dovresti vedere:

Nella lista al di sotto della timeline vedrai un mucchio di paint record. Questo è Chrome che disegna e rasterizza le texture per i livelli compositori della pagina. (Dopo che tutti gli oggetti sono stati disegnati puoi anche osservare alcuni record di livelli composti che sarebbero tutti quei livelli aggiornati che sono stati composti e pronti per la visualizzazione sullo schermo.)

Chrome DevTools: Paint Records
Paint record nel DevTools di Chrome

Vicino ad ogni paint record puoi osservare le dimensioni dell'area disegnata e, passandoci sopra il puntatore del mouse, Chrome evidenzierà l'area nella pagina che è stata ridisegnata. Un altro modo per osservare le aree che sono state ridisegnate è abilitare la voce “Show paint rectangles” nelle impostazioni di Chrome DevTool, accessibili tramite la piccola icona a forma di ingranaggio nell'angolo in basso a sinistra. Questo è il primo indicatore che hai a disposizione sul comportamento della tua pagina durante lo scrolling. Promemoria: dovresti cercare di ottenere le zone disegnate più piccole possibile.

Tramite il menu laterale puoi notare che le regioni disegnabili coprono essenzialmente l'intero schermo, e le prestazioni davvero ne soffrono. La causa di questo è che Chrome unisce le regioni danneggiate. In questo caso sono la sottile striscia dall'ampiezza 100% lungo la parte inferiore per il contenuto che è appena entrato nella visuale e il menu laterale dall'altezza 100% che Chrome deve disegnare nello stesso livello compositore; il tutto è unito in una regione di ampiezza 100% e di altezza 100%. Se disabiliti il menu laterale tramite la checkbox nella parte superiore della pagina e scrolli di nuovo noterai che l'unica ripetizione che deve essere effettuata è la striscia sul fondo, e il tutto avviene molto più velocemente.

Se vuoi osservare un esempio di tutto questo nel mondo reale, prova a caricare la pagina di Google+ e cambia la navigazione laterale da position: fixed a position: absolute. Questo modifica di sicuro il comportamento del sito, ma il punto è che così puoi notare un effettivo aumento delle prestazioni cambiando gli stili. La tua reazione a tutto questo potrebbe essere decidere di non usare mai position:fixed, ma in realtà tutto dipende dal contesto e dai requisiti. C'è un tempo e un posto per ogni cosa, l'importante è essere in grado di misurare e comprendere l'impatto delle proprie decisioni.

Ridimensionamento delle Immagini

In aggiunta ai paint record di base, per alcuni di questi abbiamo a disposizione qualche altra informazione, in partiocolare riguardo le immagini. Se ridimensioni la pagina in su e in giù durante una registrazione vedrai che alcuni dei paint record possono essere espansi e mostrare ulteriori dettagli. Se lo fai dovresti vedere alcuni image record per il ridimensionamento. Questi rappresentano proprio quello che c'è scritto sulla scatola: il browser deve ridimensinare l'immagine come parte del suo lavoro di disegno sullo schermo.

Chrome DevTools: Resize Records
Un resize record di immagine nel DevTools di Chrome

Se stai inviando immagini di grandi dimensioni ad un dispositivo, e le stai riscalando con un CSS o con i suoi attributi dimensionali, è più probabile che questo possa accadere. Ovviamente il fattore di scala che il browser deve applicare all'immagine e la frequenza con cui deve applicarlo andranno a influenzare le prestazioni della tua pagina ogni volta che questo lavoro deve essere eseguito, interrompendo quindi eventuali altri processi.

Quindi, è molto efficace inviare immagini che possono essere renderizzate senza ridimensionamento. Come nota a margine, esistono situazioni in cui le operazioni di ridimensionamento sono inevitabili, soprattutto sui dispositivi mobili, in cui lo zoom viene facilmente modificato dal valore 1 ovunque l'utente esegua un pinch-to-zoom. In questi casi puoi fare poco, ma è utile sapere che può succedere (e probabilmente succederà). Un aspetto positivo è che le cose evolvono in continuazione per quanto riguarda i browser. In particolare, il rendering è un tema caldo, quindi aspettati di veder miglorare le cose nei prossimi mesi.

Detto questo, ci sono altri elementi che possono impattare le prestazioni dello scrolling:

  • Stili elaborati
  • Reflow e repaint
  • Fallimenti del debouncing negli eventi di scroll

Affrontiamo ognuna di queste cose con maggiore dettaglio.

Stili Elaborati

Per prima cosa, va detto che non tutti gli stili sono creati uguali. Alcuni effetti come il box-shadow sono spesso nominati come particolarmente onerosi da un punto di vista di rendering, e questo perchè il codice a loro associato impiega parecchio tempo per essere eseguito se paragonato ad altri stili. Ciò implica che se utilizzi stili elaborati che richiedono numerose ripetizione, probabilmente avrai basse prestazioni. In secondo luogo, nulla resta uguale: uno stile che oggi è lento e pesante domani potrebbe essere veloce e ottimizzato, e questo cambia da browser a browser. Il concetto chiave qui è far leva sugli strumenti di sviluppo, come abbiamo fatto prima, per identificare la provenienza dei colli di bottiglia e cercare quindi di ridurre il carico di lavoro del browser.

Al Google I/O 2012 Nat Duca e Tom Wiltzius hanno parlato molto del rendering spazzatura in Chrome, fornendo un mucchio di preziosi consigli anche sugli stili elaborati e sulla misurazione del loro impatto.

Reflow e Repaint

Ogni volta che richiedi la proprietà offsetTop di un elemento in JavaScript, istantaneamente fornisci al browser un sacco di lavoro, poichè questo deve sbrigarsi a cercare nel layout della pagina in modo tale da darti la risposta esatta. Questo processo è chiamato reflow. Se quindi modifichiamo un'altra proprietà dell'elemento basato sul valore di offsetTop, l'elemento (e di conseguenza il suo livello compositore) dovrà essere ridisegnato, la qual cosa potrebbe essere faticosa. C'è anche una reazione a catena poichè, ridisegnandolo, abbiamo invalidato la stima del valore di offsetTop,visto che dal punto di vista del browser qualcosa nella pagina è stata modificata.

Il problema vero nasce quando devi riscalare una collezione di elementi. Calcolando la posizione di ogni elemento in rapida successione, forzi il browser a compiere un inutile e oneroso ciclo reflow-repaint. In queste situazioni, quello che dovresti fare è evitare questo ciclo suddividendo il lavoro in due passi: per prima cosa raccogli tutti i valori offsetTop, e subito dopo aggiorni le viste. Procedendo in questo modo aggiriamo la necessità di ricalcolare ripetutamente le posizioni dei vari elementi nella pagina e, ipotizzando di stare utilizzando requestAnimationFrame, programmeremo gli aggiornamenti delle viste in un momento ottimale per il browser.

Si ricorda che esistono altre operazioni assieme ad offsetTop che comportano l'esecuzione di un reflow, quindi varrebbe la pena controllarle così da trattarle in modo adeguato.

Debouncing per gli Eventi di Scroll

Diciamo che stai mettendo in piedi un sito in parallasse pittosto grosso. La tua prima tendenza potrebbe essere di operare gli aggiornamenti visuali quando registri un evento di scroll. Il problema principale è che gli eventi di scroll non sono programmati per gli aggiornamenti visuali del browser, cioè in una callback requestAnimationFrame, quindi corri il rischio di eseguire aggiornamenti multipli entro un singolo frame renderizzato. Se i tuoi aggiornamenti sono dispendiosi, il che è molto probabile se stai progettando qualcosa come un sito in parallasse (numerose aree danneggiate, un sacco di ridisegno e composizione), allora eseguire aggiornamenti multipli più spesso di quanto necessario non è per niente una buona idea. Per risolvere questo problema devi effettuare il debounce (antirimbalzo) degli eventi di scroll. Puoi far questo semplicemente memorizzando l'ultimo valore dello scroll in una variabile ogni volta che registri un evento di scroll, e in seguito esegui gli aggiornamenti visuali in un requestAnimationFrame, facendo uso dell'ultimo valore di scroll conosciuto. Questo significa che il browser può organizzare gli aggiornamenti visuali negli istanti di tempo corretti, e noi abbiamo ridotto il lavoro eseguito all'interno di ogni frame allo stretto indispensabile.

Abbiamo affrontato la questione dei cicli reflow / repaint in un articolo precendente, quindi può valere la pena controllare se vuoi saperne di più.

Conclusione

Ora sai come utilizzare la Timeline del DevTools di Chrome per valutare le prestazioni della tua applicazione durante uno scrolling. Ha senso ribadire che gli aspetti peculiari delle prestazioni cambiano di continuo, quindi certi elementi che oggi sono più veloci di altri possono cambiare nel tempo, e questa è la chiave per interpretare quello che leggi a riguardo e adattare il tuo approccio di conseguenza.

Dovresti sempre tenere d'occhio i tuoi veri dati sulle performace nel DevTools. Confrontare i cambiamenti a occhio alletta, ma i risultati reali possono non essere subito ovvi. Quindi non tirare a indovinare, ma esegui qualche test.

Altre Letture

Se sei interessato al funzionamento interno del browser, o se vuoi altri consigli per evitare i colli di bottiglia nelle prestazioni del rendering, dai un'occhiata qui.

  1. Tali Garsiel & Paul Irish on how browsers work
  2. A summary of hardware compositing in Chrome
  3. Paul Lewis on debouncing scroll events and reflow / repaint cycles
  4. Tom Wiltzius on jank busting for better rendering performance

Comments

0