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

Dave Tapuska
Dave Tapuska

Résumé

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

Offrir une expérience utilisateur fluide est important pour le Web. Le délai entre la réception d'un événement d'entrée et le moment où les éléments visuels sont réellement mis à jour est important, mais il est généralement moins important d'effectuer moins de travail. Dans les dernières versions de Chrome, nous avons réduit la latence d'entrée sur ces appareils.

Dans un souci de fluidité et de performances, nous apportons dans Chrome 60 une modification qui entraîne une diminution de la fréquence de ces événements tout en augmentant la précision des informations fournies. Tout comme lorsque Jelly Bean est sorti et que le Choreographer a été intégré, qui aligne les entrées sur Android, nous proposons sur le Web des entrées alignées sur le cadre sur toutes les plates-formes.

Mais parfois, vous avez besoin de plus d'événements. Ainsi, 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.

Parlons d'abord de la fréquence des événements.

Diminuer la fréquence des événements

Il faut comprendre quelques notions de base: les écrans tactiles offrent des fréquences d'entrée comprises entre 60 et 120 Hz, tandis que les souris diffusent des entrées à une fréquence généralement de 100 Hz (mais la fréquence 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 concrètement ? Cela signifie que nous recevons des entrées à un taux supérieur à celui auquel nous actualisons réellement l'affichage. Examinons donc la chronologie des performances des outils de développement pour une application de peinture simple.

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 image avec un temps de rendu incohérent. Les petits blocs jaunes indiquent des tests de positionnement pour des éléments tels que la cible de l'événement DOM, le déclenchement de l'événement, l'exécution de JavaScript, la mise à jour du nœud pointé et éventuellement recalculer la mise en page et les styles.

Chronologie des performances montrant des temps de rendu incohérents

Alors pourquoi faisons-nous un travail supplémentaire sans entraîner de mises à jour visuelles ? Idéalement, nous ne voulons pas effectuer de travail qui ne profite pas à l'utilisateur en fin de compte. À 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 constatez que le temps de rendu est plus cohérent et que le temps de traitement des événements est réduit.

Nous avons testé cette fonctionnalité dans les versions Canary et en développement. Nous avons constaté que nous effectuons 35% de tests de positionnement en moins, ce qui permet au thread principal d'être prêt à être exécuté plus souvent.

Il est important que les développeurs Web sachent que tout événement distinct (tel que keydown, keyup, mouseup, mousedown, touchstart, touchend) qui se produit sera envoyé immédiatement avec tous les événements en attente, en préservant l'ordre relatif. Lorsque cette fonctionnalité est activée, une grande partie du travail est rationalisée dans le flux normal de boucle d'événements, fournissant un intervalle d'entrée cohérent. Les événements continus sont ainsi alignés avec les événements scroll et resize, qui ont déjà été simplifiés dans le flux de la boucle d'événements dans Chrome.

Une chronologie des performances montrant un temps de rendu relativement cohérent.

Nous avons constaté que la grande majorité des applications qui utilisent de tels événements n'ont pas besoin de cette fréquence plus élevée. Android s'aligne déjà sur les événements depuis un certain nombre d'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 pour ordinateur. Il y a toujours eu un problème avec les threads principaux irréguliers, qui causait des problèmes de fluidité des entrées, ce qui signifie que vous pouvez voir des sauts de position chaque fois que l'application fonctionne, ce qui rend impossible de savoir comment le pointeur passe d'un endroit à l'autre.

La méthode getCoalescedEvents()

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

Comparaison des événements standards et fusionnés

Au lieu de recevoir un seul événement, vous pouvez accéder au tableau des événements historiques qui en sont à l'origine. Android, iOS et Windows disposent tous d'API très similaires dans leurs SDK natifs, et nous proposons 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 au niveau 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 envoyés, mais qu'ils sont juste au point pour le trajet, ils ne sont pas testés. Certains champs, tels que currentTarget et eventPhase, ont des valeurs par défaut. L'appel de méthodes liées à la distribution telles que stopPropagation() ou preventDefault() n'a aucun effet sur l'événement parent.