Eventi di input allineati

Dave Tapuska
Dave Tapuska

TL;DR

  • Chrome 60 riduce il jank diminuendo la frequenza degli eventi e migliorando così la coerenza della tempistica dei frame.
  • Il metodo getCoalescedEvents(), introdotto in Chrome 58, offre la stessa raccolta di informazioni sugli eventi che hai sempre avuto.

Offrire un'esperienza utente ottimale è importante per il web. Il tempo che intercorre tra la ricezione di un evento di input e il momento in cui le immagini vengono effettivamente aggiornate è importante e in genere è importante ridurre il lavoro necessario. Nelle ultime versioni di Chrome abbiamo ridotto la latenza di input su questi dispositivi.

Nell'interesse dell'uniformità e delle prestazioni, in Chrome 60 stiamo apportando una modifica che fa sì che questi eventi si verifichino a una frequenza inferiore, aumentando al contempo la granularità delle informazioni fornite. Proprio come quando è stato rilasciato Jelly Bean, e introdotto il Coreografo, che allinea l'input su Android, stiamo portando sul web l'input allineato al frame su tutte le piattaforme.

A volte però servono più eventi. Perciò, in Chrome 58 abbiamo implementato un metodo chiamato getCoalescedEvents(), che consente alla tua applicazione di recuperare il percorso completo del puntatore anche quando riceve meno eventi.

Parliamo innanzitutto della frequenza degli eventi.

Riduzione della frequenza degli eventi

Cerchiamo di capire alcune nozioni di base: i touch-screen inviano l'input a 60-120 Hz e i mouse inviano l'input in genere a 100 Hz (ma possono arrivare ovunque fino a 2000 Hz). Eppure, la frequenza di aggiornamento tipica di un monitor è 60 Hz. Ma che cosa significa davvero? Significa che riceviamo input a una frequenza superiore rispetto a quella aggiornata alla visualizzazione. Diamo quindi un'occhiata a una sequenza temporale del rendimento di Devtools per una semplice app per pittura su tela.

Nell'immagine seguente, con l'input allineato al requestAnimationFrame() disattivato, puoi vedere più blocchi di elaborazione per frame con una durata frame incoerente. I piccoli blocchi gialli indicano i test degli hit per elementi come il target dell'evento DOM, l'invio dell'evento, l'esecuzione di JavaScript, l'aggiornamento del nodo su cui è stato passato il mouse e possibilmente ricalcolo di layout e stili.

Una cronologia del rendimento che mostra una durata dei frame incoerente

Perché stiamo facendo lavoro aggiuntivo che non comporta aggiornamenti visivi? Idealmente, non intendiamo svolgere operazioni che in definitiva non siano a beneficio dell'utente. A partire da Chrome 60, la pipeline di input ritarda l'invio di eventi continui (wheel, mousewheel, touchmove, pointermove e mousemove) e li invia immediatamente prima dell'esecuzione del requestAnimationFrame() callback. Nell'immagine qui sotto (con la funzionalità attivata), vedi una durata frame più coerente e meno eventi di elaborazione.

Stiamo conducendo un esperimento con questa funzionalità abilitata sui canali Canary e Dev e abbiamo riscontrato che eseguiamo il 35% in meno di hit test, il che consente al thread principale di essere pronto per essere eseguito più spesso.

Una nota importante di cui gli sviluppatori web devono essere consapevoli è che ogni evento discreto (ad esempio keydown, keyup, mouseup, mousedown, touchstart, touchend) che si verifica verrà inviato immediatamente insieme a tutti gli eventi in attesa, mantenendo l'ordine relativo. Quando questa funzionalità è abilitata, gran parte del lavoro viene semplificata nel normale flusso di loop di eventi, fornendo un intervallo di input coerente. In questo modo gli eventi continui sono integrati con gli eventi scroll e resize, già semplificati nel flusso del loop di eventi in Chrome.

Una sequenza temporale del rendimento che mostra una durata dei frame relativamente coerente.

Abbiamo scoperto che la stragrande maggioranza delle applicazioni che utilizzano questi eventi non è utile per la frequenza più alta. Android ha già allineato gli eventi da diversi anni, quindi non c'è nulla di nuovo, ma i siti potrebbero riscontrare eventi meno granulari sulle piattaforme desktop. C'è sempre stato un problema con i thread principali di scarsa qualità che causano problemi di fluidità dell'input, il che significa che potresti notare dei salti in posizione ogni volta che l'applicazione funziona, rendendo impossibile sapere come il puntatore sia andato da un punto all'altro.

Il metodo getCoalescedEvents()

Come dicevo, ci sono rari casi in cui l'applicazione preferisce conoscere il percorso completo del puntatore. Pertanto, per risolvere i casi in cui si notano grandi salti e la frequenza ridotta degli eventi, in Chrome 58 abbiamo lanciato un'estensione per gli eventi di puntatore chiamata getCoalescedEvents(). Di seguito è riportato un esempio di come jank sul thread principale viene nascosto all'applicazione se utilizzi questa API.

Confronto tra eventi standard e combinati.

Anziché ricevere un singolo evento, puoi accedere all'array di eventi storici che hanno causato l'evento. Android, iOS e Windows hanno tutte API molto simili nei loro SDK nativi e stiamo esponendo un'API simile al web.

In genere un'app di disegno potrebbe aver disegnato un punto esaminando gli offset dell'evento:

window.addEventListener("pointermove", function(event) {
    drawPoint(event.pageX, event.pageY);
});

Questo codice può essere facilmente modificato per utilizzare l'array di eventi:

window.addEventListener("pointermove", function(event) {
    var events = 'getCoalescedEvents' in event ? event.getCoalescedEvents() : [event];
    for (let e of events) {
    drawPoint(e.pageX, e.pageY);
    }
});

Tieni presente che non tutte le proprietà negli eventi uniti sono compilate. Poiché gli eventi uniti non vengono realmente inviati, ma sono pronti per essere usati, non vengono sottoposti a test. Alcuni campi, come currentTarget e eventPhase, avranno i valori predefiniti. La chiamata di metodi correlati all'invio, come stopPropagation() o preventDefault(), non avrà alcun effetto sull'evento padre.