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.

Lorsque l'utilisateur effectue un geste, comme un toucher sur un écran, c'est le processus du navigateur qui reçoit le geste en premier lieu. 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 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é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 compositeur pouvait gérer le défilement de manière fluide en composant des couches rastérisées. Si aucun écouteur d'événement d'entrée n'est associé à la page, le thread du moteur de composition peut créer un nouveau frame composite complètement indépendant du thread principal. Mais que se passerait-il si des écouteurs d'événements étaient associés à la page ? Comment le thread du 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 compilateur marque une région de la page à laquelle des gestionnaires d'événements sont associés en tant que "Région non à défilement rapide". 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.

zone à défilement limité et non rapide
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 la page entière à 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 que le défilement vertical a peut-être commencé au moment où vous souhaitez utiliser preventDefault afin de limiter la direction de défilement. Pour ce faire, utilisez 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és 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.

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

Dans l'article précédent, nous avons vu que l'écran standard actualise l'écran 60 fois par seconde et comment suivre la cadence pour une animation fluide. Pour l'entrée, un appareil à écran tactile standard génère des événements tactiles 60 à 120 fois par seconde, et une souris standard génère des événements 100 fois par seconde. L'événement d'entrée est plus fidèle que ce 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écution de JavaScript par rapport à la lenteur d'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 distincts tels que keydown, keyup, mouseup, mousedown, touchstart et touchend sont envoyé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 une application de dessin et que vous placez un tracé en fonction de coordonnées touchmove, vous risquez de perdre des coordonnées entre les coordonnées pour tracer une ligne lisse. 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: Chemin gestuel pour un geste fluide à 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 aux raisons pour lesquelles DevTools recommande d'ajouter {passive: true} à votre gestionnaire d'événements ou pourquoi vous pourriez écrire un attribut async dans votre tag de script, j'espère que cette série vous aidera à comprendre 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 ce qui fonctionne bien 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 lui convient le mieux. 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 politique relative aux fonctionnalités 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 souhaitez 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é, l'exécution du code JavaScript bloquant l'analyseur est bloqué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. Être gentil avec le navigateur en organisant notre code, à son tour, améliore votre expérience utilisateur. J'espère que vous me rejoindrez dans cette quête de gentillesse envers les navigateurs.

Merci beaucoup à 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 et Charlie Yasuda.{/13

Avez-vous apprécié 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.