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

La mise en page permet au navigateur de déterminer les informations géométriques des éléments (leur taille et leur position dans la page). Chaque élément comporte des informations de taille explicites ou implicites basées sur le code CSS utilisé, son contenu ou un élément parent. Ce processus s'appelle "Mise en page" dans Chrome.

La mise en page permet au navigateur de déterminer les informations géométriques des éléments (leur taille et leur emplacement dans la page). Chaque élément comporte des informations de taille explicites ou implicites basées sur le code CSS utilisé, son contenu ou un élément parent. Ce processus s'appelle Mise en page dans Chrome (et dans les navigateurs dérivés tels que 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 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 DOM de la page.
  2. La complexité de ces mises en page.

Résumé

  • La mise en page a un effet direct sur la latence des interactions
  • La mise en page est normalement limitée à l'ensemble du document.
  • Le nombre d'éléments DOM affecte les performances. Évitez de déclencher la mise en page autant que possible.
  • Évitez les mises en page synchrones forcées et le thrashing de mise en page. Lisez les valeurs de style, puis modifiez-les.

Les 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. La latence d'interaction désigne le temps nécessaire pour qu'une interaction se termine (qui se termine lorsque le navigateur affiche l'image suivante pour afficher les résultats). Cet aspect des performances de la page est mesuré par la métrique Interaction to Next Paint.

Le temps nécessaire au navigateur pour présenter l'image suivante 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 qu'un problème s'est produit. Les mises à jour visuelles peuvent impliquer un certain travail de mise en page pour atteindre cet objectif.

Afin de maintenir l'INP de votre site Web le plus bas possible, il est important d'éviter la mise en page lorsque cela est possible. S'il est impossible 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 l'image suivante 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 le calcul de la mise en page et pour mettre à jour 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 s'applique presque toujours à l'ensemble du document. Si vous avez beaucoup d'éléments, déterminer leur emplacement et leurs dimensions va prendre beaucoup de temps.

S'il n'est pas possible d'éviter la mise en page, la solution consiste à utiliser à nouveau 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 "Timeline", cliquez sur "Enregistrer", puis interagissez avec votre site. Lorsque vous arrêtez l'enregistrement, vous pouvez consulter le détail des performances de votre site:

Les outils de développement s'affichent depuis longtemps dans Layout.

En examinant la trace dans l'exemple ci-dessus, nous constatons que la mise en page passe plus de 28 millisecondes dans la mise en page pour chaque image, ce qui, lorsqu'il nous reste 16 millisecondes pour afficher une image à l'écran dans une animation, est beaucoup trop élevée. Vous pouvez également voir que les outils de développement vous indiquent la taille de l'arborescence (1 618 éléments dans le cas présent) 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 pas toujours. 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 couplé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 correspond à l'ordre suivant:

Utilisation de Flexbox comme mise en page

Le code 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 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 à garder à l'esprit est que lorsque JavaScript s'exécute, toutes les anciennes valeurs de mise en page du frame précédent sont connues et peuvent être interrogées. Ainsi, si vous voulez, par exemple, écrire la hauteur d'un élément (appelons-le "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);
}

Cela pose problème si vous modifiez les styles de la boîte 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 concernant la hauteur, le navigateur doit d'abord appliquer le changement de style (grâce à 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 exécuter en premier (où le navigateur peut utiliser les valeurs de mise en page du frame précédent), puis effectuer les écritures:

Si elle était correctement effectuée, la fonction ci-dessus serait la suivante:

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 besoin d'appliquer des styles, puis de rechercher des valeurs ; l'utilisation des valeurs du dernier cadre devrait être suffisante. L'exécution des calculs de style et de la mise en page de manière synchrone et avant que le navigateur ne le souhaite, vous crée peut-être des goulots d'étranglement.

É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 rapidement. 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 à la largeur 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). À 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. Cette opération se produira à chaque itération.

La correction de cet exemple consiste à une nouvelle fois à lire, puis à écrire:

// Read.
const width = box.offsetWidth;

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

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

Image principale de Unsplash, de Hal Gatewood.