Éviter les mises en page volumineuses et complexes et le thrashing de mise en page

La mise en page est l'endroit où le navigateur détermine les informations géométriques des éléments (leur taille et leur emplacement sur la page). Chaque élément aura des informations de taille explicites ou implicites basées sur le code CSS utilisé, le contenu de l'élément ou un élément parent. Ce processus s'appelle "Mise en page" dans Chrome.

La mise en page est l'endroit où le navigateur détermine les informations géométriques des éléments: leur taille et leur emplacement sur la page. Chaque élément aura des informations de taille explicites ou implicites basées sur le code CSS utilisé, le contenu de l'élément ou un élément parent. Ce processus s'appelle "Mise en page" dans Chrome (et les navigateurs dérivés comme Edge) et Safari. Dans Firefox, cela s'appelle Reflow, mais le processus est en fait le même.

Comme pour les calculs de style, les problèmes immédiats liés au coût de la mise en page sont les suivants:

  1. Nombre d'éléments nécessitant une mise en page, qui est un sous-produit de la taille DOM de la page.
  2. La complexité de ces mises en page

Résumé

  • La mise en page influe directement sur la latence des interactions
  • La mise en page s'applique normalement à l'ensemble du document.
  • Le nombre d'éléments DOM aura une incidence sur les performances. Dans la mesure du possible, vous devez éviter de déclencher la mise en page.
  • Évitez les mises en page synchrones forcées et le thrashing de mise en page. Lisez les valeurs de style, puis apportez des modifications de style.

Effets de la mise en page sur la latence d'interaction

Lorsqu'un utilisateur interagit avec la page, ces interactions doivent être aussi rapides que possible. Le temps nécessaire à l'exécution d'une interaction (qui se termine lorsque le navigateur présente l'image suivante afin d'afficher les résultats de l'interaction) est appelé latence d'interaction. Il s'agit d'un aspect des performances de la page que la métrique Interaction to Next Paint mesure.

Le temps nécessaire au navigateur pour présenter le frame suivant en réponse à une interaction de l'utilisateur est appelé délai de présentation de l'interaction. L'objectif d'une interaction est de fournir un retour visuel afin de signaler à l'utilisateur que quelque chose s'est produit, et les mises à jour visuelles peuvent nécessiter un certain travail de mise en page afin d'atteindre cet objectif.

Pour que l'INP de votre site Web soit aussi faible que possible, il est important d'éviter la mise en page dans la mesure du possible. S'il n'est pas possible d'éviter complètement la mise en page, il est important de limiter ce travail de mise en page afin que le navigateur puisse afficher rapidement le cadre suivant.

Évitez la mise en page autant que possible.

Lorsque vous modifiez des styles, le navigateur vérifie si l'une des modifications nécessite le calcul de la mise en page et pour la mise à jour de l'arborescence de rendu. Les modifications apportées aux "propriétés géométriques", telles que les largeurs, les hauteurs, la gauche ou la partie supérieure, doivent toutes être mises en page.

.box {
  width: 20px;
  height: 20px;
}

/**
  * Changing width and height
  * triggers layout.
  */

.box--expanded {
  width: 200px;
  height: 350px;
}

La mise en page couvre presque toujours l'intégralité du document. Si vous avez beaucoup d'éléments, il faudra beaucoup de temps pour déterminer leur emplacement et leurs dimensions.

S'il n'est pas possible d'éviter la mise en page, utilisez une nouvelle fois les outils pour les développeurs Chrome afin de déterminer le temps nécessaire et de déterminer si la mise en page est à l'origine d'un goulot d'étranglement. Tout d'abord, ouvrez les outils de développement, accédez à l'onglet "Chronologie", cliquez sur "Enregistrer" et interagissez avec votre site. Lorsque vous arrêtez l'enregistrement, les performances de votre site s'affichent:

Les outils de développement affichent un long délai dans la mise en page.

En explorant la trace de l'exemple ci-dessus, nous constatons que plus de 28 millisecondes sont passées dans la mise en page pour chaque image, ce qui, lorsque nous avons 16 millisecondes pour afficher une image à l'écran dans une animation, est beaucoup trop élevé. Vous pouvez également constater que les outils de développement vous indiquent la taille de l'arborescence (1 618 éléments dans ce cas) et le nombre de nœuds nécessitant une mise en page (5 dans ce cas).

Gardez à l'esprit qu'il est généralement conseillé d'éviter la mise en page dans la mesure du possible. Toutefois, il n'est pas toujours possible de l'éviter. Si vous ne pouvez pas éviter la mise en page, sachez que son coût est lié à la taille du DOM. Bien que la relation entre les deux ne soit pas étroitement liée, les DOM plus volumineux entraînent généralement des coûts de mise en page plus élevés.

Éviter les mises en page synchrones forcées

L'envoi d'un cadre à l'écran se déroule comme suit:

Utilisation de Flexbox comme mise en page.

JavaScript s'exécute d'abord, puis les calculs de style, puis la mise en page. Il est toutefois possible de forcer un navigateur à effectuer une mise en page antérieure avec JavaScript. C'est ce qu'on appelle la mise en page synchrone forcée.

Tout d'abord, gardez à l'esprit que lorsque JavaScript exécute toutes les anciennes valeurs de mise en page du frame précédent, elles sont connues et vous pouvez les interroger. Ainsi, si vous souhaitez écrire la hauteur d'un élément (appelons-la "box") au début du cadre, vous pouvez écrire le code suivant:

// Schedule our function to run at the start of the frame:
requestAnimationFrame(logBoxHeight);

function logBoxHeight () {
  // Gets the height of the box in pixels and logs it out:
  console.log(box.offsetHeight);
}

Un problème peut survenir si vous avez modifié les styles de la zone avant de demander sa hauteur:

function logBoxHeight () {
  box.classList.add('super-big');

  // Gets the height of the box in pixels and logs it out:
  console.log(box.offsetHeight);
}

Pour répondre à la question de hauteur, le navigateur doit d'abord appliquer le changement de style (en raison de l'ajout de la classe super-big), puis puis exécuter la mise en page. Ce n'est qu'alors qu'il pourra renvoyer la bonne hauteur. Il s'agit d'un travail inutile et potentiellement coûteux.

Pour cette raison, vous devez toujours regrouper vos lectures de style et les effectuer en premier (où le navigateur peut utiliser les valeurs de mise en page du frame précédent), puis effectuer les écritures:

Si c'est le cas, la fonction ci-dessus serait:

function logBoxHeight () {
  // Gets the height of the box in pixels and logs it out:
  console.log(box.offsetHeight);

  box.classList.add('super-big');
}

Dans la plupart des cas, vous ne devriez pas avoir à appliquer des styles, puis à interroger des valeurs ; utiliser les valeurs du dernier frame devrait suffire. L'exécution des calculs de style et de la mise en page de manière synchrone et antérieure à ce que souhaite le navigateur représente des goulots d'étranglement potentiels.

Éviter le thrashing de mise en page

Il existe un moyen d'aggraver les mises en page synchrones forcées: en effectuer un grand nombre à la suite rapide. Examinez ce code:

function resizeAllParagraphsToMatchBlockWidth () {
  // Puts the browser into a read-write-read-write cycle.
  for (let i = 0; i < paragraphs.length; i++) {
    paragraphs[i].style.width = `${box.offsetWidth}px`;
  }
}

Ce code boucle sur un groupe de paragraphes et définit la largeur de chaque paragraphe pour qu'elle corresponde à celle d'un élément appelé "box". Cela semble assez inoffensif, mais le problème est que chaque itération de la boucle lit une valeur de style (box.offsetWidth), puis l'utilise immédiatement pour mettre à jour la largeur d'un paragraphe (paragraphs[i].style.width). Lors de l'itération suivante de la boucle, le navigateur doit tenir compte du fait que les styles ont changé depuis la dernière demande de offsetWidth (lors de l'itération précédente). Il doit donc appliquer les modifications de style et exécuter la mise en page. Cela se produira à chaque itération.

La correction de cet exemple consiste à lire, puis à écrire à nouveau les valeurs:

// Read.
const width = box.offsetWidth;

function resizeAllParagraphsToMatchBlockWidth () {
  for (let i = 0; i < paragraphs.length; i++) {
    // Now write.
    paragraphs[i].style.width = `${width}px`;
  }
}

Si vous souhaitez garantir la sécurité, envisagez d'utiliser FastDOM, qui regroupe automatiquement vos lectures et écritures pour vous, et qui vous évite de déclencher des mises en page synchrones forcées ou du thrashing de mise en page accidentellement.

Image principale tirée de Unsplash, par Hal Gatewood.