Corriger l'instabilité de la mise en page

Tutoriel sur l'utilisation de WebPageTest pour identifier et résoudre les problèmes d'instabilité de la mise en page.

Dans un article précédent, j'ai expliqué comment mesurer le CLS (Cumulative Layout Shift) dans WebPageTest. Le CLS est une agrégation de tous les décalages de mise en page. Dans cet article, j'ai donc pensé qu'il serait intéressant d'aller plus loin et d'inspecter chaque décalage de mise en page sur une page pour essayer de comprendre ce qui pourrait être à l'origine de l'instabilité et de résoudre le ou les problèmes.

L'API Layout Instability permet d'obtenir la liste de tous les événements de décalage de mise en page d'une page :

new Promise(resolve => {
  new PerformanceObserver(list => {
    resolve(list.getEntries().filter(entry => !entry.hadRecentInput));
  }).observe({type: "layout-shift", buffered: true});
}).then(console.log);

Cela génère un tableau de décalages de mise en page qui ne sont pas précédés d'événements d'entrée :

[
  {
    "name": "",
    "entryType": "layout-shift",
    "startTime": 210.78500000294298,
    "duration": 0,
    "value": 0.0001045969445437389,
    "hadRecentInput": false,
    "lastInputTime": 0
  }
]

Dans cet exemple, il n'y a eu qu'un très léger décalage de 0,01 % à 210 ms.

Connaître l'heure et la gravité du changement est utile pour identifier les causes possibles du changement. Revenons à WebPageTest, un environnement d'atelier permettant d'effectuer d'autres tests.

Mesure des décalages de mise en page dans WebPageTest

Comme pour le CLS dans WebPageTest, la mesure des décalages de mise en page individuels nécessite une métrique personnalisée. Heureusement, le processus est plus facile maintenant que Chrome 77 est stable. L'API Layout Instability est activée par défaut. Vous devriez donc pouvoir exécuter cet extrait JS sur n'importe quel site Web dans Chrome 77 et obtenir des résultats immédiatement. Dans WebPageTest, vous pouvez utiliser le navigateur Chrome par défaut, sans vous soucier des indicateurs de ligne de commande ni de l'utilisation de Canary.

Modifions ce script pour générer une métrique personnalisée pour WebPageTest :

[LayoutShifts]
return new Promise(resolve => {
  new PerformanceObserver(list => {
    resolve(JSON.stringify(list.getEntries().filter(entry => !entry.hadRecentInput)));
  }).observe({type: "layout-shift", buffered: true});
});

La promesse de ce script se résout en une représentation JSON du tableau plutôt qu'en un tableau lui-même. En effet, les métriques personnalisées ne peuvent produire que des types de données primitifs, comme des chaînes ou des nombres.

Le site Web que j'utiliserai pour le test est ismyhostfastyet.com, un site que j'ai créé pour comparer les performances de chargement réelles des hébergeurs Web.

Identifier les causes de l'instabilité de la mise en page

Dans les résultats, nous pouvons voir que la métrique personnalisée LayoutShifts a la valeur suivante:

[
  {
    "name": "",
    "entryType": "layout-shift",
    "startTime": 3087.2349999990547,
    "duration": 0,
    "value": 0.3422101449275362,
    "hadRecentInput": false,
    "lastInputTime": 0
  }
]

Pour résumer, un seul décalage de mise en page de 34,2 % se produit à 3 087 ms. Pour identifier le coupable, utilisons la vue pellicule de WebPageTest.

Deux cellules de la pellicule, montrant des captures d'écran avant et après le changement de mise en page.
Deux cellules de la pellicule, montrant des captures d'écran avant et après le changement de mise en page.

Le défilement jusqu'à la marque d'environ trois secondes dans la pellicule nous montre exactement la cause de ce décalage de mise en page de 34 % : le tableau coloré. Le site Web récupère un fichier JSON de manière asynchrone, puis le affiche dans une table. La table est initialement vide. Le décalage est donc dû au fait d'attendre qu'elle soit remplie lors du chargement des résultats.

En-tête de police Web qui apparaît de nulle part
En-tête de police Web qui apparaît de nulle part.

Mais ce n'est pas tout. Lorsque la page s'affiche entièrement au bout de 4,3 secondes environ, nous pouvons voir que le <h1> de la page "Is my host yet?" (Mon hôte est-il rapide encore ?) apparaît-il de nulle part. Cela se produit parce que le site utilise une police Web et n'a pris aucune mesure pour optimiser le rendu. La mise en page ne semble pas se décaler lorsque cela se produit, mais l'expérience utilisateur à attendre si longtemps est médiocre pour lire le titre.

Correction de l'instabilité de la mise en page

Maintenant que nous savons que la table générée de manière asynchrone entraîne le décalage d'un tiers de la fenêtre d'affichage, il est temps de résoudre ce problème. Nous ne connaissons pas le contenu du tableau tant que les résultats JSON ne sont pas chargés, mais nous pouvons tout de même le renseigner avec des données d'espace réservé afin que la mise en page elle-même soit relativement stable lorsque le DOM est affiché.

Voici le code permettant de générer des données d'espace réservé :

function getRandomFiller(maxLength) {
  var filler = '█';
  var len = Math.ceil(Math.random() * maxLength);
  return new Array(len).fill(filler).join('');
}

function getRandomDistribution() {
  var fast = Math.random();
  var avg = (1 - fast) * Math.random();
  var slow = 1 - (fast + avg);
  return [fast, avg, slow];
}

// Temporary placeholder data.
window.data = [];
for (var i = 0; i < 36; i++) {
  var [fast, avg, slow] = getRandomDistribution();
  window.data.push({
    platform: getRandomFiller(10),
    client: getRandomFiller(5),
    n: getRandomFiller(1),
    fast,
    avg,
    slow
  });
}
updateResultsTable(sortResults(window.data, 'fast'));

Les données d'espace réservé sont générées de manière aléatoire avant d'être triées. Elle inclut le caractère "█" répété un nombre aléatoire de fois pour créer des espaces réservés visuels pour le texte et une distribution générée de manière aléatoire des trois valeurs principales. J'ai également ajouté des styles pour désaturer toutes les couleurs du tableau afin de montrer clairement que les données ne sont pas encore entièrement chargées.

L'apparence des espaces réservés que vous utilisez n'a pas d'importance pour la stabilité de la mise en page. L'objectif des espaces réservés est de rassurer les utilisateurs sur le fait que le contenu sera disponible et que la page n'est pas défectueuse.

Voici à quoi ressemblent les espaces réservés pendant le chargement des données JSON :

Le tableau de données est affiché avec des données d&#39;espace réservé.
La table de données est affichée avec des données d'espace réservé.

La résolution du problème lié aux polices Web est beaucoup plus simple. Comme le site utilise Google Fonts, il nous suffit de transmettre la propriété display=swap dans la requête CSS. C'est tout. L'API Fonts ajoute le style font-display: swap dans la déclaration de police, ce qui permet au navigateur d'afficher immédiatement le texte dans une police de remplacement. Voici le balisage correspondant, avec la correction incluse:

<link href="https://fonts.googleapis.com/css?family=Chivo:900&display=swap" rel="stylesheet">

Valider les optimisations

Après avoir réexécuté la page via WebPageTest, nous pouvons générer une comparaison avant/après pour visualiser la différence et mesurer le nouveau degré d'instabilité de la mise en page:

Filmstrip WebPageTest montrant le chargement côte à côte des deux sites avec et sans optimisation de la mise en page.
Pellicule WebPageTest montrant les deux sites qui se chargent côte à côte avec et sans optimisation de la mise en page.
[
  {
    "name": "",
    "entryType": "layout-shift",
    "startTime": 3070.9349999997357,
    "duration": 0,
    "value": 0.000050272187989256116,
    "hadRecentInput": false,
    "lastInputTime": 0
  }
]

D'après la métrique personnalisée, un décalage de mise en page se produit toujours à 3 071 ms (à peu près en même temps qu'avant), mais sa gravité est beaucoup plus faible: 0,005%. Je peux vivre avec.

Il apparaît également clairement dans la pellicule que la police <h1> revient immédiatement à une police système, ce qui permet aux utilisateurs de la lire plus tôt.

Conclusion

Les sites Web complexes connaîtront probablement beaucoup plus de changements de mise en page que dans cet exemple, mais le processus de correction reste le même : ajoutez des métriques d'instabilité de la mise en page à WebPageTest, croisez les résultats avec la bande de chargement visuelle pour identifier les coupables, puis implémentez un correctif à l'aide de repères pour réserver l'espace à l'écran.

(Encore une chose) Mesurer l'instabilité de la mise en page ressentie par de vrais utilisateurs

Il est agréable de pouvoir exécuter WebPageTest sur une page avant et après une optimisation, et de constater une amélioration d'une métrique, mais ce qui compte vraiment, c'est que l'expérience utilisateur s'améliore. N'est-ce pas pour cela que nous essayons d'améliorer le site ?

Il serait donc idéal de commencer à mesurer l'instabilité de la mise en page des utilisateurs réels en plus de nos métriques de performances Web traditionnelles. Il s'agit d'un élément essentiel de la boucle de rétroaction d'optimisation, car les données sur le terrain nous indiquent où se trouvent les problèmes et si nos corrections ont eu un impact positif.

En plus de collecter vos propres données d'instabilité de mise en page, consultez le rapport sur l'expérience utilisateur Chrome, qui inclut des données de décalage de mise en page cumulé issues d'expériences utilisateur réelles sur des millions de sites Web. Il vous permet de connaître les performances de votre site (ou de vos concurrents) ou d'explorer l'état de l'instabilité de la mise en page sur le Web.