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

Mariko Kosaka

L'entrée est transmise au compositeur

Il s'agit de la dernière partie de la série de blogs en quatre parties qui explore Chrome et examine comment il gère notre code pour afficher un site Web. Dans l'article précédent, nous avons examiné le processus de rendu et découvert le moteur de rendu. Dans cet article, nous allons voir comment le moteur de rendu permet une interaction fluide lorsque l'utilisateur saisit des entrées.

Événements d'entrée du point de vue du navigateur

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

Lorsqu'un geste utilisateur, tel qu'un appui sur un écran, se produit, c'est le processus du navigateur qui reçoit le geste en premier. Toutefois, le processus du navigateur ne sait que où ce geste s'est produit, car le contenu d'un onglet est géré par le processus de rendu. Le processus du navigateur envoie donc le type d'événement (par exemple, touchstart) et ses coordonnées au processus du moteur de rendu. Le processus 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énements 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 des événements d'entrée

Figure 2: Volet d'affichage au-dessus des calques de page

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

Comprendre la région non à défilement 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 compilateur marque une région de la page à laquelle des gestionnaires d'événements sont associés en tant que "Région non cliquable". Grâce à ces informations, le thread du compositeur peut s'assurer d'envoyer l'é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 du compositeur continue de composer un nouveau frame sans attendre le thread principal.

Région limitée à défilement lent
Figure 3: Schéma de la saisie décrite dans la région non à défilement rapide

Faites attention lorsque vous écrivez des gestionnaires d'événements

La délégation d'événements est un modèle de gestion des événements courant en développement Web. Étant donné que les événements se propagent, vous pouvez associer un gestionnaire d'événements à l'élément le plus élevé et déléguer des tâches en fonction de la cible de l'événement. Vous avez peut-être vu ou écrit du code semblable à celui ci-dessous.

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

Étant donné que vous n'avez besoin d'écrire qu'un seul 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 désormais marquée comme une région non déroulable. Cela signifie que même si votre application ne se soucie pas de l'entrée 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 est reçu. Par conséquent, la fonctionnalité de défilement fluide du compositeur est annulée.

Région de page complète à défilement lent
Figure 4: Schéma de l'entrée décrite dans la région non à défilement rapide couvrant une page entière

Pour éviter cela, vous pouvez transmettre des options passive: true dans 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 fixée au défilement horizontal

Imaginons que vous souhaitiez limiter la direction de défilement d'une zone d'une page au défilement horizontal uniquement.

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 le défilement vertical peut avoir commencé au moment où vous souhaitez preventDefault afin de limiter la direction de défilement. Vous pouvez le vérifier à 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 la cible de l'événement

test de positionnement
Figure 6: Le thread principal examine les enregistrements de peinture et demande ce qui est dessiné au point x,y

Lorsque le thread du moteur de rendu envoie un événement d'entrée au thread principal, un test de contact est exécuté en premier pour trouver la cible de l'événement. Le test de collision 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 où l'événement s'est produit.

Minimiser les distributions d'événements vers le thread principal

Dans l'article précédent, nous avons expliqué comment notre écran d'affichage typique actualise l'écran 60 fois par seconde et comment nous devons suivre la cadence pour une animation fluide. Pour l'entrée, un appareil à écran tactile standard génère des événements tactiles entre 60 et 120 fois par seconde, tandis qu'une souris standard génère des événements 100 fois par seconde. L'événement d'entrée est plus fidèle que l'écran ne peut l'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 contact et d'exécution JavaScript par rapport à la lenteur de l'actualisation de l'écran.

événements non filtrés
Figure 7: Événements inondant la chronologie des frames, ce qui entraîne des à-coups sur la page

Pour réduire les appels excessifs au thread principal, Chrome fusionne les événements continus (tels que wheel, mousewheel, mousemove, pointermove et touchmove) et retarde l'envoi jusqu'à la veille du prochain requestAnimationFrame.

événements fusionnés
Figure 8: Même chronologie qu'auparavant, mais l'événement est fusionné et retardé

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

Utiliser getCoalescedEvents pour obtenir des événements intra-frame

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 tels qu'une application de dessin et que vous placez un tracé basé sur des coordonnées touchmove, vous risquez de perdre des coordonnées intermédiaires pour dessiner une ligne fluide. Dans ce cas, vous pouvez utiliser la méthode getCoalescedEvents dans l'événement du pointeur pour obtenir des informations sur ces événements fusionnés.

getCoalescedEvents
Figure 9: Trajet de geste tactile fluide à gauche, trajet 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 vu le fonctionnement interne d'un navigateur Web. Si vous n'avez jamais réfléchi à la raison pour laquelle DevTools recommande d'ajouter {passive: true} à votre gestionnaire d'événements ou pourquoi vous pourriez écrire l'attribut async dans votre balise de script, j'espère que cette série vous éclairera sur la raison pour laquelle 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 respectueux du navigateur, mais que vous ne savez pas par où commencer, Lighthouse est un outil qui effectue un audit de n'importe quel site Web et vous fournit un rapport sur ce qui est bien fait et ce qui doit être amélioré. La lecture de la liste des audits vous donne également une idée des éléments qui intéressent un navigateur.

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 sur la mesure des performances de votre site.

Ajouter le règlement sur les fonctionnalités à votre site

Si vous souhaitez aller plus loin, la règle de fonctionnalité est une nouvelle fonctionnalité de la plate-forme Web qui peut vous servir de garde-fou lorsque vous créez votre projet. Activer le règlement sur les fonctionnalités garantit le comportement de votre application et vous évite de commettre des erreurs. Par exemple, si vous voulez vous assurer que votre application ne bloquera jamais l'analyse, vous pouvez l'exécuter sur la stratégie de scripts synchrones. Lorsque sync-script: 'none' est activé, l'exécution du code JavaScript bloquant l'analyseur est empêchée. Cela empêche votre code de bloquer l'analyseur, et le navigateur n'a pas besoin de s'inquiéter de la mise en pause de l'analyseur.

Conclusion

merci

Lorsque j'ai commencé à créer des sites Web, je ne me souciais presque que de la façon dont j'écrivais mon code et de ce qui m'aiderait à être plus productif. Ces éléments sont importants, mais nous devons également réfléchir à la façon dont le navigateur récupère le code que nous écrivons. Les navigateurs modernes ont investi et continuent d'investir dans des moyens de fournir une meilleure expérience Web aux utilisateurs. En organisant notre code, nous faisons preuve de gentillesse envers le navigateur, ce qui améliore l'expérience utilisateur. J'espère que vous me rejoindrez dans cette quête de gentillesse envers les navigateurs.

Un grand merci à tous ceux qui ont examiné 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, Nasko Oskov et Charlie Reis.

Avez-vous aimé cette série ? Si vous avez des questions ou des suggestions pour un prochain post, n'hésitez pas à me contacter dans la section des commentaires ci-dessous ou sur Twitter en suivant @kosamari.