Événements d'entrée alignés

Dave Tapuska
Dave Tapuska

TL;DR

  • Chrome 60 réduit les à-coups en diminuant la fréquence des événements, ce qui améliore la cohérence du timing des frames.
  • La méthode getCoalescedEvents(), introduite dans Chrome 58, fournit la même richesse d'informations sur les événements que vous aviez toujours.

Il est important de proposer une expérience utilisateur fluide sur le Web. Le temps écoulé entre la réception d'un événement d'entrée et la mise à jour des visuels est important, et il est généralement important de faire moins de travail. Au cours des dernières versions de Chrome, nous avons réduit la latence d'entrée sur ces appareils.

Pour des raisons de fluidité et de performances, nous apportons un changement dans Chrome 60 qui entraîne la survenue de ces événements à une fréquence plus faible, tout en augmentant la précision des informations fournies. Tout comme lors de la sortie de Jelly Bean et de l'introduction de Choreographer, qui aligne les entrées sur Android, nous apportons l'entrée alignée sur les frames au Web sur toutes les plates-formes.

Mais parfois, vous avez besoin de plus d'événements. Par conséquent, dans Chrome 58, nous avons implémenté une méthode appelée getCoalescedEvents(), qui permet à votre application de récupérer le chemin d'accès complet du pointeur même lorsqu'elle reçoit moins d'événements.

Commençons par parler de la fréquence des événements.

Réduire la fréquence des événements

Voyons quelques principes de base: les écrans tactiles envoient des entrées à 60-120 Hz, et les souris envoient des entrées généralement à 100 Hz (mais cela peut aller jusqu'à 2 000 Hz). Pourtant, la fréquence d'actualisation typique d'un écran est de 60 Hz. Qu'est-ce que cela signifie en réalité ? Cela signifie que nous recevons des entrées à un débit supérieur à celui auquel nous actualisons l'écran. Examinons une chronologie des performances dans les outils de développement pour une application de peinture simple sur canevas.

Dans l'image ci-dessous, lorsque l'entrée alignée sur requestAnimationFrame() est désactivée, vous pouvez voir plusieurs blocs de traitement par frame avec un délai de frame incohérent. Les petits blocs jaunes indiquent les tests de contact pour des éléments tels que la cible de l'événement DOM, le traitement de l'événement, l'exécution de JavaScript, la mise à jour du nœud survolé et le calcul éventuel de la mise en page et des styles.

Une chronologie des performances montrant un timing des images incohérent

Pourquoi effectuons-nous un travail supplémentaire qui ne génère aucune mise à jour visuelle ? Dans l'idéal, nous ne voulons pas effectuer de travail qui ne profite pas à l'utilisateur. À partir de Chrome 60, le pipeline d'entrée retardera l'envoi des événements continus (wheel, mousewheel, touchmove, pointermove, mousemove) et les enverra juste avant le rappel requestAnimationFrame(). Dans l'image ci-dessous (avec la fonctionnalité activée), vous pouvez voir un temps de frame plus cohérent et moins de temps de traitement des événements.

Nous avons effectué un test avec cette fonctionnalité activée sur les canaux Canary et Dev. Nous avons constaté que nous effectuions 35% de tests de requêtes en moins, ce qui permet au thread principal d'être prêt à s'exécuter plus souvent.

Les développeurs Web doivent savoir qu'un événement discret (tel que keydown, keyup, mouseup, mousedown, touchstart ou touchend) qui se produit est immédiatement distribué avec les événements en attente, ce qui préserve l'ordre relatif. Lorsque cette fonctionnalité est activée, une grande partie du travail est simplifiée dans le flux normal de la boucle d'événements, ce qui fournit un intervalle d'entrée cohérent. Les événements continus sont ainsi mis en conformité avec les événements scroll et resize, qui ont déjà été simplifiés dans le flux de boucle d'événements dans Chrome.

Une chronologie des performances montrant un timing des images relativement cohérent.

Nous avons constaté que la grande majorité des applications qui consomment ces événements n'ont pas besoin de la fréquence plus élevée. Android a déjà aligné les événements depuis plusieurs années. Il n'y a donc rien de nouveau, mais les sites peuvent rencontrer des événements moins précis sur les plates-formes de bureau. Il y a toujours eu un problème avec les threads principaux incohérents qui provoquent des à-coups pour la fluidité de la saisie. Cela signifie que vous pouvez voir des sauts de position chaque fois que l'application effectue une tâche, ce qui rend impossible de savoir comment le pointeur est passé d'un point à un autre.

Méthode getCoalescedEvents()

Comme je l'ai indiqué, il existe de rares cas où l'application préfère connaître le chemin d'accès complet du pointeur. Pour résoudre le problème de sauts importants et de fréquence réduite des événements, nous avons lancé une extension des événements de pointeur appelée getCoalescedEvents() dans Chrome 58. Vous trouverez ci-dessous un exemple de la façon dont les à-coups sur le thread principal sont masqués de l'application si vous utilisez cette API.

Comparaison des événements standards et coalescents.

Au lieu de recevoir un seul événement, vous pouvez accéder à la matrice des événements historiques à l'origine de l'événement. Android, iOS et Windows ont tous des API très similaires dans leurs SDK natifs, et nous exposons une API similaire sur le Web.

En règle générale, une application de dessin peut avoir dessiné un point en examinant les décalages de l'événement:

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

Ce code peut facilement être modifié pour utiliser le tableau d'événements:

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

Notez que toutes les propriétés des événements fusionnés ne sont pas renseignées. Étant donné que les événements fusionnés ne sont pas vraiment distribués, mais qu'ils sont simplement là pour le trajet, ils ne sont pas testés. Certains champs, tels que currentTarget et eventPhase, auront leurs valeurs par défaut. Appeler des méthodes liées à la distribution telles que stopPropagation() ou preventDefault() n'aura aucun effet sur l'événement parent.