Au cœur du navigateur Web moderne (partie 4)

Mariko Kosaka

L'entrée arrive dans le compositeur

Ceci est le dernier de la série de quatre articles de blog portant sur Chrome, qui explique comment il gère le code permettant d'afficher un site Web. Dans le post précédent, nous avons examiné le processus de rendu et découvert le compositeur. Dans cet article, nous verrons comment le compositeur permet une interaction fluide lors de l'entrée utilisateur.

Saisir les événements du point de vue du navigateur

Lorsque vous entendez des "événements d'entrée", vous ne pensez peut-être qu'à une saisie dans une zone de texte ou à un clic de souris. Du point de vue du navigateur, l'entrée désigne tout geste de l'utilisateur. Le défilement de la molette de la souris est un événement d'entrée, tandis que le toucher ou le survol de la souris est également un événement d'entrée.

Lorsque l'utilisateur effectue un geste comme un geste sur un écran, le processus du navigateur est celui qui reçoit le geste en premier. Toutefois, le processus du navigateur ne connaît que l'endroit où ce geste s'est produit, car le contenu d'un onglet est géré par le processus de moteur de rendu. Le processus du navigateur envoie donc le type d'événement (comme touchstart) et ses coordonnées au processus du moteur de rendu. Le processus du moteur de rendu gère l'événement de manière appropriée en recherchant la cible de l'événement et en exécutant les écouteurs d'événement associés.

événement d'entrée
Figure 1: Événement d'entrée acheminé via le processus du navigateur vers le processus du moteur de rendu

Le compositeur reçoit les événements d'entrée

Figure 2: Fenêtre d'affichage passant au-dessus des calques de page

Dans l'article précédent, nous avons vu comment le compositeur pouvait gérer le défilement de manière fluide en compilant des calques rastérisés. Si aucun écouteur d'événement d'entrée n'est associé à la page, le thread compositeur peut créer un frame composite entièrement indépendant du thread principal. Mais que se passerait-il si certains écouteurs d'événements étaient joints à la page ? Comment le thread compositeur peut-il savoir si l'événement doit être géré ?

Comprendre la région à défilement non rapide

Étant donné que l'exécution de JavaScript est la tâche du thread principal, lorsqu'une page est composée, le thread du compositeur marque une région de la page dans laquelle des gestionnaires d'événements sont associés en tant que "Région à défilement non rapide". Grâce à ces informations, le thread compositeur peut s'assurer d'envoyer un événement d'entrée au thread principal si l'événement se produit dans cette région. Si l'événement d'entrée provient de l'extérieur de cette région, le thread compositeur continue de composer une nouvelle trame sans attendre le thread principal.

zone limitée à défilement non rapide
Figure 3: Schéma de l'entrée décrite pour la région à défilement non rapide

Tenez compte lorsque vous écrivez des gestionnaires d'événements

La délégation d'événements est un modèle de gestion d'événements courant en développement Web. Comme les événements apparaissent sous forme de bulle, vous pouvez associer un gestionnaire d'événements à l'élément supérieur et déléguer des tâches en fonction de la cible de l'événement. Vous avez peut-être vu ou écrit un code semblable à celui ci-dessous.

document.body.addEventListener('touchstart', event => {
    if (event.target === area) {
        event.preventDefault();
    }
});

Étant donné qu'il suffit d'écrire un gestionnaire d'événements pour tous les éléments, l'ergonomie de ce modèle de délégation d'événements est attrayante. Toutefois, si vous examinez ce code du point de vue du navigateur, la page entière est maintenant marquée comme une région à défilement rapide. Cela signifie que même si votre application ne se soucie pas des entrées provenant de certaines parties de la page, le thread du compositeur doit communiquer avec le thread principal et l'attendre chaque fois qu'un événement d'entrée arrive. Ainsi, la fonctionnalité de défilement fluide du compositeur est contournée.

zone à défilement non rapide pleine page
Figure 4: Schéma de l'entrée décrite pour la zone à défilement non rapide couvrant une page entière

Pour éviter cela, vous pouvez transmettre les options passive: true à votre écouteur d'événements. Cela indique au navigateur que vous souhaitez toujours écouter l'événement dans le thread principal, mais que le compositeur peut également composer un nouveau frame.

document.body.addEventListener('touchstart', event => {
    if (event.target === area) {
        event.preventDefault()
    }
 }, {passive: true});

Vérifier si l'événement peut être annulé

défilement de la page
Figure 5: Page Web dont une partie est fixe au défilement horizontal

Imaginons que vous ayez une zone dans une page dont vous souhaitez limiter la direction de défilement au défilement horizontal.

L'utilisation de l'option passive: true dans votre événement de pointeur signifie que le défilement de la page peut être fluide, mais que le défilement vertical peut avoir commencé au moment où vous souhaitez preventDefault afin de limiter la direction de défilement. Vous pouvez vérifier cela à l'aide de la méthode event.cancelable.

document.body.addEventListener('pointermove', event => {
    if (event.cancelable) {
        event.preventDefault(); // block the native scroll
        /*
        *  do what you want the application to do here
        */
    }
}, {passive: true});

Vous pouvez également utiliser une règle CSS telle que touch-action pour éliminer complètement le gestionnaire d'événements.

#area {
  touch-action: pan-x;
}

Trouver l'événement cible

test de positionnement
Figure 6: Thread principal examinant les enregistrements de peinture demandant ce qui est dessiné sur le point x.y

Lorsque le thread compositeur envoie un événement d'entrée au thread principal, la première chose à exécuter est un test de positionnement pour trouver la cible de l'événement. Le test de positionnement utilise les données des enregistrements de peinture générées lors du processus de rendu pour déterminer ce qui se trouve sous les coordonnées du point dans lequel l'événement s'est produit.

Réduire les distributions d'événements au thread principal

Dans l'article précédent, nous avons expliqué comment notre écran classique actualise l'écran 60 fois par seconde et comment nous devons suivre la cadence pour une animation fluide. En entrée, un appareil à écran tactile classique envoie des événements tactiles entre 60 et 120 fois par seconde, et une souris classique envoie des événements 100 fois par seconde. L'événement d'entrée a une fidélité supérieure à celle que notre écran peut actualiser.

Si un événement continu tel que touchmove a été envoyé au thread principal 120 fois par seconde, il peut déclencher un nombre excessif de tests de positionnement et d'exécutions JavaScript par rapport à la lenteur de l'actualisation de l'écran.

événements non filtrés
Figure 7: Événements qui inondent la chronologie du frame, ce qui provoque des à-coups sur la page

Pour limiter le nombre d'appels au thread principal, Chrome regroupe les événements continus (tels que wheel, mousewheel, mousemove, pointermove, touchmove) et retarde l'envoi jusqu'au requestAnimationFrame suivant.

événements fusionnés
Figure 8: Même chronologie que précédemment, mais événement fusionné et retardé

Tous les événements distincts tels que keydown, keyup, mouseup, mousedown, touchstart et touchend sont envoyés immédiatement.

Utiliser getCoalescedEvents pour obtenir des événements intraframe

Pour la plupart des applications Web, les événements fusionnés devraient suffire à offrir une bonne expérience utilisateur. Toutefois, si vous créez des éléments comme dessiner une application et placer un tracé en fonction des coordonnées touchmove, vous risquez de perdre entre les coordonnées pour tracer une ligne lisse. Dans ce cas, vous pouvez utiliser la méthode getCoalescedEvents dans l'événement de pointeur pour obtenir des informations sur ces événements fusionnés.

getCoalescedEvents
Figure 9: Chemin de gestes lisses à gauche et tracé limité fusionné à droite
window.addEventListener('pointermove', event => {
    const events = event.getCoalescedEvents();
    for (let event of events) {
        const x = event.pageX;
        const y = event.pageY;
        // draw a line using x and y coordinates.
    }
});

Étapes suivantes

Dans cette série, nous avons abordé le fonctionnement interne d'un navigateur Web. Si vous n'avez jamais réfléchi à la raison pour laquelle les outils de développement recommandent d'ajouter {passive: true} à votre gestionnaire d'événements ou à la raison pour laquelle vous pourriez écrire l'attribut async dans votre balise de script, j'espère que cette série de messages explique pourquoi un navigateur a besoin de ces informations pour offrir une expérience Web plus rapide et plus fluide.

Utiliser Lighthouse

Si vous souhaitez que votre code soit agréable pour le navigateur, mais que vous ne savez pas par où commencer, Lighthouse est un outil qui exécute un audit de n'importe quel site Web, et vous fournit un rapport sur les actions efficaces et les améliorations à apporter. La liste des audits vous donne également une idée du type de chose qu'un navigateur prend en compte.

Découvrez comment mesurer les performances

Les ajustements des performances peuvent varier d'un site à l'autre. Il est donc essentiel de mesurer les performances de votre site et de déterminer ce qui convient le mieux à votre site. L'équipe des outils pour les développeurs Chrome propose quelques tutoriels pour apprendre à mesurer les performances de votre site.

Ajouter une règle de caractéristiques à votre site

Si vous souhaitez effectuer une étape supplémentaire, sachez que les règles relatives aux caractéristiques sont une nouvelle fonctionnalité de la plate-forme Web qui peut vous servir de garde-fous lorsque vous compilez votre projet. L'activation des règles de fonctionnalité garantit le comportement de votre application et vous évite de faire des erreurs. Par exemple, pour vous assurer que votre application ne bloquera jamais l'analyse, vous pouvez l'exécuter sur une stratégie de scripts synchrones. Lorsque sync-script: 'none' est activé, le code JavaScript qui bloque les analyseurs ne peut pas s'exécuter. Cela permet d'éviter que votre code ne bloque l'analyseur, et le navigateur n'a pas à se soucier de la mise en veille de l'analyseur.

Synthèse

merci

Lorsque j'ai commencé à créer des sites Web, je me suis presque seulement soucié de la façon dont j'écrirais mon code et de ce qui m'aiderait à être plus productif. Ces choses sont importantes, mais nous devons aussi réfléchir à la façon dont le navigateur prend le code que nous écrivons. Les navigateurs modernes investissent et continuent d'investir dans des moyens d'améliorer l'expérience Web des utilisateurs. Être gentil avec le navigateur en organisant notre code, améliore l'expérience utilisateur. Rejoignez-moi dans cette quête de sympathie avec les navigateurs !

Un grand merci à toutes les personnes qui ont lu les premières versions de cette série, y compris (mais sans s'y limiter): Alex Russell, Paul Irish, Meggin Kearney, Eric Bidelman, Mathias Bynens, Addy Osmani, Kinuko Yasuda, Kinuko Yasuda,

Avez-vous apprécié cette série ? Si vous avez des questions ou des suggestions pour le prochain post, n'hésitez pas à nous en faire part dans la section des commentaires ci-dessous ou à l'adresse @kosamari sur Twitter.