É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 dispose d'informations de dimensionnement explicites ou implicites en fonction du CSS utilisé, du contenu de l'élément ou d'un élément parent. Ce processus est appelé "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 dispose d'informations de dimensionnement explicites ou implicites en fonction du CSS utilisé, du contenu de l'élément ou d'un élément parent. Ce processus est appelé "mise en page" dans Chrome (et les navigateurs dérivés tels qu'Edge) et Safari. Dans Firefox, il s'agit de Reflow, mais le processus est en fait le même.

Comme pour les calculs de style, les préoccupations immédiates concernant le coût de la mise en page sont les suivantes :

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

Résumé

  • La mise en page a un impact direct sur la latence d'interaction
  • La mise en page est généralement limitée à l'ensemble du document.
  • Le nombre d'éléments DOM affecte les performances. Dans la mesure du possible, évitez de déclencher la mise en page.
  • Évitez les mises en page synchrones forcées et les mises en page inefficaces. Lisez les valeurs de style, puis apportez des modifications de style.

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

Lorsque l'utilisateur interagit avec la page, ces interactions doivent être aussi rapides que possible. Le temps nécessaire pour qu'une interaction se termine (qui se termine lorsque le navigateur présente le frame suivant pour afficher les résultats de l'interaction) est appelé latence d'interaction. C'est 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 pour signaler à l'utilisateur qu'un événement s'est produit. Les mises à jour visuelles peuvent nécessiter un certain travail de mise en page pour 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 lorsque cela est possible. S'il n'est pas possible d'éviter complètement la mise en page, il est important de limiter ce travail afin que le navigateur puisse présenter le frame suivant rapidement.

Éviter la mise en page dans la mesure du possible

Lorsque vous modifiez des styles, le navigateur vérifie si l'une des modifications nécessite un calcul de mise en page et une mise à jour de l'arborescence de rendu. Les modifications apportées aux "propriétés géométriques", telles que la largeur, la hauteur, la gauche ou le haut, nécessitent toutes une mise en page.

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

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

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

La mise en page est presque toujours limitée à l'ensemble du document. Si vous avez beaucoup d'éléments, il vous faudra beaucoup de temps pour déterminer leurs emplacements et dimensions.

Si vous ne pouvez pas éviter la mise en page, la clé est de réutiliser les outils pour les développeurs Chrome pour voir combien de temps cela prend et 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, un récapitulatif des performances de votre site s'affiche :

Les outils de développement affichent un temps long 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 voir que DevTools indique 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 que le conseil général est d'éviter la mise en page dans la mesure du possible, mais ce n'est pas toujours possible. 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 frame à un écran se fait dans l'ordre suivant :

Utilisation de Flexbox comme mise en page.

Le code JavaScript est d'abord exécuté, puis les calculs de style, puis la mise en page. Il est toutefois possible de forcer un navigateur à effectuer la mise en page plus tôt avec JavaScript. C'est ce qu'on appelle la mise en page synchrone forcée.

La première chose à retenir est que lorsque le code JavaScript s'exécute, toutes les anciennes valeurs de mise en page du frame précédent sont connues et disponibles pour vous. Par exemple, si vous souhaitez écrire la hauteur d'un élément (appelons-le "boîte") au début du frame, vous pouvez écrire du code comme suit :

// 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);
}

Les choses se compliquent 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.

Par conséquent, vous devez toujours regrouper vos lectures de style et les effectuer en premier (lorsque le navigateur peut utiliser les valeurs de mise en page du frame précédent), puis effectuer les écritures :

Si elle est correctement réalisée, la fonction ci-dessus se présente comme suit :

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 n'avez pas besoin d'appliquer des styles, puis d'interroger des valeurs. L'utilisation des 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 la mise en page

Il existe un moyen de rendre les mises en page synchrones forcées encore pires : en en effectuant beaucoup de suite. 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 la prochaine itération de la boucle, le navigateur doit tenir compte du fait que les styles ont changé depuis la dernière requête 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 produit à chaque itération.

Pour résoudre ce problème, vous devez à nouveau lire, puis écrire des 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, et devrait vous empêcher de déclencher accidentellement des mises en page synchrones forcées ou des rafales de mise en page.