Corriger l'instabilité de la mise en page

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

Dans un article précédent, j'avais écrit sur la mesure du 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'examiner plus en détail chaque décalage de mise en page sur une page pour essayer de comprendre ce qui pourrait être à l'origine de l'instabilité et essayer de résoudre les problèmes.

Mesurer les décalages de mise en page

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

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

Vous obtenez ainsi un tableau de décalages de mise en page non 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, un changement minime de 0,01% à 210 ms s'est produit.

Connaître l'heure et la gravité du changement est utile pour déterminer ce qui aurait pu causer le changement. Revenons à WebPageTest afin d'effectuer d'autres tests dans un environnement d'atelier.

Mesurer les décalages de mise en page dans WebPageTest

Comme pour mesurer le CLS dans WebPageTest, mesurer les décalages de mise en page individuels nécessitera 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. Avec WebPageTest, vous pouvez utiliser le navigateur Chrome par défaut, sans avoir à vous soucier des indicateurs de ligne de commande ni de Canary.

Modifions donc ce script afin de 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 que du tableau lui-même. En effet, les métriques personnalisées ne peuvent produire que des types de données primitifs tels que des chaînes ou des nombres.

Le site Web que je vais utiliser pour le test est ismyhostfastyet.com, un site que j'ai créé pour comparer les performances de chargement réelles des hôtes Web.

Identifier les causes d'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 en 3 087 ms. Pour identifier le problème, utilisons la vue Pellicule de WebPageTest.

Deux cellules dans la pellicule, affichant des captures d'écran avant et après le décalage de mise en page.
Deux cellules dans la pellicule, avec des captures d'écran avant et après le décalage de mise en page.

Si vous faites défiler la pellicule jusqu'à environ trois secondes, nous verrons exactement quelle est la cause de ce décalage de 34 % : la table colorée. Le site Web récupère un fichier JSON de manière asynchrone, puis l'affiche dans une table. La table étant initialement vide, le décalage est dû au fait d'attendre qu'il soit rempli lorsque les résultats sont chargés.

En-tête de police Web apparaissant de nulle part.
En-tête de police Web apparaissant de nulle part

Mais ce n'est pas tout. Lorsque la page est entièrement terminée au bout d'environ 4,3 secondes, nous pouvons voir que le <h1> de la page "Is my host yet?" (Mon hébergeur est-il rapide ?) apparaît-il de nulle part. En effet, le site utilise une police Web et n'a pris aucune mesure pour optimiser l'affichage. La mise en page ne semble pas se déplacer dans ce cas, mais cela nuit à l'expérience utilisateur de devoir attendre aussi longtemps pour lire le titre.

Corriger 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, vous pouvez résoudre ce problème. Nous ne connaissons pas le contenu de la table tant que les résultats JSON n'ont pas été chargés, mais nous pouvons quand même remplir la table avec des données d'espace réservé afin que la mise en page soit relativement stable lors du rendu du DOM.

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 aléatoirement des trois valeurs principales. J'ai également ajouté des styles pour désaturer toutes les couleurs du tableau afin d'indiquer 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. Les espaces réservés permettent d'informer les utilisateurs que le contenu arrive et que la page est opérationnelle.

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

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

Traiter le problème de police Web est beaucoup plus simple. Comme le site utilise des polices 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 à 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">

Vérifier les optimisations

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

Pellicule WebPageTest affichant les deux sites se chargeant côte à côte 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
  }
]

Selon la métrique personnalisée, un décalage de la mise en page se produit toujours à 3 071 ms (à peu près en même temps qu'auparavant), mais la gravité de ce décalage 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 rapidement.

Conclusion

Les sites Web complexes subiront probablement beaucoup plus de décalages de mise en page que dans cet exemple, mais le processus de résolution reste le même: ajoutez des métriques d'instabilité de la mise en page à WebPageTest, comparez les résultats avec la pellicule de chargement visuel pour identifier les coupables et mettez en œuvre un correctif en utilisant des espaces réservés pour réserver l'espace d'écran.

(Une dernière chose) Mesurer l'instabilité de la mise en page ressentie par les utilisateurs réels

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 en premier lieu d'améliorer le site ?

Ce qui serait intéressant, c'est de commencer à mesurer l'instabilité de la mise en page chez les utilisateurs réels en même temps que nos métriques de performances Web traditionnelles. Il s'agit d'un élément essentiel de la boucle de rétroaction pour l'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 sur l'instabilité de la mise en page, consultez le rapport d'expérience utilisateur Chrome, qui inclut des données sur le décalage cumulatif issues des expériences utilisateur réelles sur des millions de sites Web. Il vous permet de connaître vos performances (ou celles de vos concurrents) ou de l'utiliser pour étudier l'instabilité de la mise en page sur le Web.