Google PageSpeed - LightHouse 6 : CLS (Cumulative Layout Shift) et LCP (Largest Contentful Paint)

Logo

Introduction

LightHouse 6 est sorti mi-mai 2020. Disponible en lignes de commandes avec Google PageSpeed, Lighthouse 6 est embarqué dans Chrome - Outils de développement depuis mi-juillet 2020 (Chrome 84).

Quoi de neuf dans la version 6 ? Le calcul du score de la performance est modifié avec l’introduction de 3 nouvelles métriques tandis que 3 autres métriques sont supprimées.

Métriques de performance dans la version 5 :

Performance metrics version 5

Métriques de performance dans la version 6 :

Performance metrics version 6

Dans la version 6, les métriques obsolètes (deprecated) sont :

  • FMP (First Meaningful Paint) : cette métrique était trop variable.
  • FCI (First CPU Idle) : pas assez distinct de la métrique TTI (Time to Interactive).
  • Max Potential First Input Delay : statistique qui n’est plus affichée dans le rapport mais elle est toujours disponible dans le fichier de résultats JSON en lignes de commandes. Il est recommandé maintenant de consulter la métrique TBT (Total Blocking Time).

3 nouvelles métriques sont introduites dans la version 6 et sont utilisées pour calculer le score de la performance :

  • TBT (Total Blocking Time) : métrique pas vraiment nouvelle et déjà disponible avec Lighthouse 5, mais elle n’etait pas utilisée pour calculer le score. TBT remplace FCI, FCI était utilisée pour mesurer le moment où une page est prête à accepter une action utilisateur (clic ou tap), TBT mesure le degré de non-interactivité d’une page.
  • LCP (Largest Contentful Paint) : mesure du chargement perçu par l’utilisateur plus réaliste. Elle marque le point lors du chargement de la page lorsque le contenu principal a été chargé et est visible par l’utilisateur.
  • CLS (Cumulative Layout Shift) : LA nouvelle statistique la plus intéressante. Elle mesure la stabilité visuelle en quantifiant le déplacement visuel du contenu d’une page. Les décalages (insertion de contenu dynamique, images, chargements des polices…) peuvent générer une mauvaise expérience utilisateur.

Avec les métriques LCP et CLS, la notation de Lighthouse 6 est plus réaliste et proche de l’expérience utilisateur.

Lighthouse 6 | Deprecated, New

Cet article se concentre principalement sur la nouvelle métrique CLS : comment diagnostiquer les éléments instables ?

Poids des métriques - Seuils

Les pondérations ont également été mises à jour dans LightHouse 6. Avec les nouvelles règles de calculs, les scores de performance diminuent considérablement dans la version 6 par rapport à la version 5 pour une page. Un outil est disponible en ligne pour comparer et définir les objectifs : Lighthouse scoring calculator.

Score weights

Les métriques TBT, LCP et CLS comptent maintenant pour 55% dans le score de performance d’une page avec LightHouse 6 :

Métrique Poids Métrique Poids
FCP - First Contentful Paint 15% TTI - Time To Interactive 15%
SI - Speed Index 15% TBT - Total Blocking Time 25%
LCP - Largest Contentful Paint 25% CLS - Cumulative Layout Shift 5%

Dans les prochaines versions, le poids du score CLS sera probablement revu à la hausse. Actuellement il n’est que de 5% car c’est encore une nouvelle mesure quelque peu expérimentale.

Les seuils de scores de qualité par métrique sont les suivants :

FCP TTI
SI TBT
LCP CLS

CLS - Cumulative Layout Shift

Une mauvaise expérience utilisateur

La vidéo ci-dessous publiée sur le site web Web.dev explique très bien ce que mesure la métrique CLS et comment les décalages peuvent causer de réels dommages. Une page se charge, l’utilisateur peut cliquer ou taper sur des éléments, mais malheureusement, du contenu est injecté dynamiquement, les boutons sont alors décalés : Oups au lieu d’annuler une commande, l’utilisateur clique sur le bouton de validation.

Fondamentalement, la métrique CLS mesure les changements de mises en page des éléments qui se produisent : des éléments DOM ajoutés dynamiquement au dessus du contenu existant, des images ou des vidéos aux dimensions inconnues, des polices qui s’affichent en plus gros ou plus petit que la police de secours, une annonce ou un widget qui se redimensionne dynamiquement…

Score CLS - recommandations

CLS est la somme totale de tous les scores de changement de mise en page individuels qui se produisent de façon inattendue dans la fenêtre d’affichage (viewport) et ceci pour toute la durée de vie de la page. Un bon score CLS doit être inférieur à 0,1.

CLS

Pour calculer le score de décalage de mise en page, le navigateur examine la taille de la fenêtre et le mouvement des éléments instables dans la fenêtre entre deux cadres rendus. Le score est le produit de deux mesures de ce mouvement : la fraction d’impact et la fraction de distance. Pour plus d’informations sur le calcul du score : Web.dev - Layout shift score.

Résultats Lighthouse et CLS

Dans le rapport LightHouse (Chrome - Outils de développement Lighthouse), les éléments du DOM qui contribuent le plus au score CLS de la page sont affichés dans la section "Avoid Large Layout Shifts" :

Perf CLS Perf CLS - Contribution

Avec Lighthouse en lignes de commandes : dans le fichier de résultats JSON, le score CLS est stocké dans la clé ['lighthouseResult']['audits']['cumulative-layout-shift'].

            "cumulative-layout-shift": {
                "id": "cumulative-layout-shift",
                "title": "Cumulative Layout Shift",
                "description": "Cumulative Layout Shift measures the movement of visible elements within the viewport.",
                "score": 0.02,
                "scoreDisplayMode": "numeric",
                "displayValue": "1.569",
                …
                "numericValue": 1.5685546391150804
            },

et les éléments du DOM qui contribuent le plus au score CLS de la page sont listés dans la clé ['lighthouseResult']['audits']['layout-shift-elements'].

            "layout-shift-elements": {
                "id": "layout-shift-elements",
                "title": "Avoid large layout shifts",
                "description": "These DOM elements contribute most to the CLS of the page.",
                "score": null,
                "scoreDisplayMode": "informative",
                "displayValue": "5 elements found",
                "details": {
                    …
                    "items": [
                        {
                            "node": {
                                "snippet": "<div id=\"col-left\">",
                                "selector": "body > div#wrap > div#col-left",
                                "nodeLabel": "Sur le même sujet\nBases de données Time Series\nInfluxDB v2, prise en main. Prép…",
                                "path": "1,HTML,1,BODY,1,DIV,5,DIV",
                                "type": "node"
                            },
                            "score": 1.2030659115893447
                        },
                        {
                            "node": {
                                "snippet": "<nav class=\"topbar-nav menu\">",
                                "selector": "body > div#wrap > nav.topbar-nav",
                                "nodeLabel": "Sybase\nMS SQL\nOracle\nMySQL\nMariaDB\nPostgreSQL\nTime Series\nNoSQL\nUnix-Linux\nConc…",
                                "path": "1,HTML,1,BODY,1,DIV,3,NAV",
                                "type": "node"
                            },
                            "score": 0.341083267664828
                        },
                        {
                            "node": {
                                "snippet": "<div>",
                                "selector": "body > div#wrap > div",
                                "nodeLabel": "Home  Article | InfluxDB - InfluxDB Server\n   ",
                                "path": "1,HTML,1,BODY,1,DIV,4,DIV",
                                "type": "node"
                            },
                            "score": 0.02320975471193952
                        },
                        {
                            "node": {
                                "snippet": "<a href=\"/\" alt=\"SQL Pour Administrateurs & Concepteurs\" title=\"SQL Pour Administrateurs & Concepteurs\">",
                                "selector": "div#wrap > header > div.hleft > a",
                                "nodeLabel": "SQL Pour Administrateurs & Concepteurs",
                                "path": "1,HTML,1,BODY,1,DIV,0,HEADER,0,DIV,1,A",
                                "type": "node"
                            },
                            "score": 0.0004195429472025217
                        },
                        …
                    ],
                    "type": "table"
                }
            },

Suivi précis du score CLS avec Google Chrome - Outils de développement (onglet Performance)

Au moment de la rédaction (fin août 2020), la fonctionnalité présentée ci-dessous n’est actuellement disponible que dans Google Chrome Canary, elle n’est pas encore livrée dans le navigateur officiel Chrome.

Google Chrome Canary est la version de développement et peut être instable mais son installation est indépendante d’une distribution Google Chrome existante.

Les outils de développement de Google Chrome (onglet Performances) incluent une nouvelle fonctionnalité intéressante pour suivre les changements de mise en page : les changements de mise en page qui se produisent sont affichés dans la chronologie dans la rubrique "Expérience". Un score est calculé pour chaque changement de mise en page, score ajouté dans la métrique CLS.

Chrome Dev Tools - Performance Tab - Experience - CLS

Dans l’onglet Résumé, pour chaque changement de mise en page, en plus du score et du score cumulé, les déplacements sont détaillés (emplacements, tailles) :

Chrome Dev Tools - Performance Tab - Experience - CLS with summary

Placer le pointeur de la souris sur le libellé "Moved from" puis le libellé "Moved to" pour visualiser graphiquement le changement de disposition d’un élément dans la page :

Chrome Dev Tools - Performance Tab - Experience - Moved from, moved to

Dans le résumé, une autre information utile : "Had recent input". Les changements de disposition qui se produisent dans les 500 millisecondes après une action utilisateur ont l’indicateur hadRecentInput défini, ils peuvent être exclus des calculs.

Optimisation du score CLS

Honnêtement, les seuils de score CLS fixés à 0,1 et 0,25 sont un défi, surtout si du contenu est ajouté ou supprimé dynamiquement avec Javascript. De mauvais scores CLS sont constatés dans les contextes suivants :

  • images sans dimensions
  • annonces (ads), embeds et iframes sans dimensions
  • contenu injecté dynamiquement après chargement de la page
  • polices web
  • actions en attente d’une réponse réseau avant de mettre à jour l’arbre DOM

Se concentrer sur les éléments contribuant le plus au score CLS score de la page.

Dans le cas pratique ci-dessous, les scores CLS des articles SQLPAC sont optimisés de 1,2-0,9 à environ 0,007 en quelques étapes.

Mesures Moyenne CLS Écart-type
Étape 0 : Version initiale 100 0,904 0,170
Étape 4 : Étape intermédiaire 100 0,187 0,019
Étape 5 : Étape finale 100 0,007 0,000073

Les mesures sont effectuées à l’aide d’un script Python et stockées dans une table MySQL. À propos de ce programme : Mesurer et stocker les métriques des performances des pages avec les API Google PageSpeed Insights et Python.

Les moyennes, minima, maxima et écarts-types sont calculés avec la syntaxe SQL ci-dessous (Étape 1 dans l’exemple de code) :

SELECT url, flag, count(*),
       avg(cumulativeLayoutShift),
       min(cumulativeLayoutShift),
       max(cumulativeLayoutShift),
       stddev_pop(cumulativeLayoutShift)
FROM `perf_pagespeed` 
where flag='Step 1'
group by flag, url
order by date_measure desc

Le squelette HTML de départ est le suivant :

  • <nav class="topbar-menu"> : conteneur des menus
  • <ul class="metismenu"> : menu horizontal (une ligne)
  • <div class="sitemap"> : conteneur du sitemap
  • <div id="col-right"> * : colonne de droite
  • <section> * : conteneur dans la colonne de droite
  • <div class="js-toc"> * : table des matières

* : conteneurs injectés dynamiquement avec Javascript

Un dessin est plus explicite :

SQLPAC HTML skeleton

height, min-height

Dans la mesure du possible, appliquer une taille fixe ou une taille minimale pour les conteneurs dans lesquels du contenu est injecté dynamiquement.

Le score CLS est considérablement réduit en définissant une hauteur fixe pour le menu supérieur et le plan du site (tailles définies empiriquement) et comme attendu, l’écart-type chute considérablement aussi. Dans l’état initial, l’écart-type est proche du score à atteindre (0,1) :

Mesures Moyenne CLS Min CLS Max CLS Écart-type
Étape 0 : Version initiale 100 0,904 0,789 1,319 0,170
Étape 1 : Menus - Hauteurs fixes
.topbar-nav { height: 92px; }

.topbar-nav > ul.metismenu {
  display:flex;
  flex-direction: row;
  height: 46px;
}
100 0,652 0,417 0,940 0,135
Étape 2 : Sitemap - Hauteur fixe
div.sitemap { height: 52px;  }
100 0,586 0,363 0,886 0,135

De grandes améliorations, mais le score est toujours mauvais suivant les critères de Google, d’autant plus que l’écart-type est toujours supérieur à 0,1, seuil au delà duquel le score CLS n’est plus considéré comme bon.

Dans les 2 prochaines étapes :

  • La colonne de droite (#col-right) n’est plus dynamiquement insérée par Javascript, le conteneur vide est ajouté dans le squelette HTML avec une hauteur minimale définie.
  • Des hauteurs minimum sont définies pour les boîtes injectées dynamiquement dans la colonne de droite.
  • Ajouté dans le squelette HTML, le conteneur de la table des matières (js-toc) n’est également plus inséré dynamiquement avec Javascript. Une hauteur minimale est définie en fonction du nombre d’éléments dans la table des matières.

Même vides, plus les conteneurs sont prédéfinis dans le squelette HTML avec au moins des hauteurs minimales (min-height), plus le score CLS diminue significativement, ainsi que l’écart-type.

Mesures Moyenne CLS Min CLS Max CLS Écart-type
Étape 3 : Stabilisation de la colonne de droite
<!-- Conteneur vide ajouté dans le squelette HTML -->
<div id="col-right"></div>
#col-right           { min-height: 1000px; }
#col-right section   { min-height: 250px; }
#col-right #topten   { min-height: 800px; }
100 0,325 0,316 0,427 0,027
Étape 4 : Stabilisation de la table des matières
<!-- Conteneur vide ajouté dans le squelette HTML -->
<div class="js-toc-wrap js-toc-large"></div>
div[class*="js-toc-wrap"]     { min-height: 160px; }
div[class*="js-toc-medium1"]  { min-height: 210px; }
div[class*="js-toc-medium2"]  { min-height: 260px; }
div[class*="js-toc-medium3"]  { min-height: 320px; }
div[class*="js-toc-large"]    { min-height: 380px; }
100 0,187 0,008 0,255 0,019

CSS différé définissant des propriétés influençant le score CLS (overflow…)

Le score est désormais inférieur à 0,2, c’est mieux mais pas assez. Un bon score est inférieur à 0.1 ! Dans les étapes précédentes, tous les éléments pouvant être optimisés ont été corrigés. Alors pourquoi le score est-il encore trop élevé et proche de 0,2 ?

Il est très difficile de trouver l’origine du problème, les fichiers de résultats JSON de Pagespeed ne donnent aucun indice, ils ne mettent en évidence que les éléments de la fenêtre pour lesquels un changement de disposition se produit. Réponse : faire attention à la propriété  CSS overflow définie dans une feuille de styles CSS différée.

Dans ce cas pratique, la propriété overflow: auto; est appliquée aux conteneurs pre et code dans une feuille de style chargée en différé, après l’évènement DOMContentLoaded, donc après la première "peinture" de la page (first painting). Lorsque la page est repeinte, les barres de défilement nécessaires (scrollbars) provoquent des changements de disposition des éléments dans la fenêtre d’affichage (viewport), même si les barres de défilement sont très éloignées de cette fenêtre.

Toutes les propriétés CSS susceptibles de générer des décalages dans la fenêtre d’affichage (overflow…) doivent être définis dans le chemin critique CSS et non de manière différée. Excellent, le score est désormais de 0,007, très bon score loin du seuil 0,1 ! L’écart-type est d’environ 0,00008 : la page est stable et sa stabilité est prédictible et fiable.

Mesures Moyenne CLS Min CLS Max CLS Écart-type
Étape 5 : Propriété overflow déplacée d’une feuille de styles chargée en différé vers la feuille de styles CSS principale.
pre > code, pre {
  display: block;
  padding: .25em; 
  margin: .25em 0;
  overflow: auto;
  font-family: Consolas, Monaco, 'Andale Mono',
               'Ubuntu Mono', monospace;
  font-size: 1em;
  line-height: 1.5;
}
100 0,007 0,0067 0,0074 0,000073

Éléments sans dimensions

Sans surprise, pour certains éléments (images, videos, ads, iframes…), définir les dimensions afin d’éviter les changements de disposition, surtout pour les éléments immédiatement visibles dans la fenêtre d’affichage (viewport) :

Mesures Moyenne CLS Min CLS Max CLS Écart-type
Première image dans le viewport sans dimensions
<img src="./images/01.png"
     alt="PostgreSQL Schema">
100 0,0201 0,0210 0,0288 0,0102
Première image dans le viewport avec dimensions
<img src="./images/01.png"
     width="380" height="476" alt="PostgreSQL Schema">
100 0,0022 0,0015 0,0024 0,00013

LCP - Largest Contentful Paint

FMP produisait des résultats incohérents et a donc été supprimé avec LightHouse 6 dans les calculs du score. LCP fournit des résultats plus précis et représente 25% du score de performance avec LightHouse 6. LCP marque le point où le plus gros élément à charger dans le contenu est visible, ce qui donne à l’utilisateur l’impression qu’une page est complètement chargée. C’est la meilleure métrique pour mesurer la vitesse du site Web.

Pour une fois, Google a fixé des seuils raisonnables et réalistes. LCP est bon lorsqu’il est inférieur à 2,5 secondes et considéré comme médiocre lorsqu’il est supérieur à 4 secondes.

CLS

Juste un petit mot sur la recherche du dernier élément LCP, savoir quel élément est le dernier peut être un indicateur utile.

LCP est détaillé dans l’onglet Performance (Chrome - Outils de développement) : chronologie, dernier élément (type, taille…).

LCP Performance tab

Le dernier élément LCP est également disponible dans le rapport LightHouse (Chrome - Outils de développement Lighthouse) : section "Largest Contentful Paint element"

LCP Lighthouse report

Avec LightHouse en lignes de commandes :  dans le fichier de résultats JSON, le dernier élément LCP est dans la clé ['lighthouseResult']['audits']['largest-contentful-paint-element'].

            "largest-contentful-paint-element": {
                "id": "largest-contentful-paint-element",
                "title": "Largest Contentful Paint element",
                "description": "This is the element that was identified as the Largest Contentful Paint. [Learn More](https://web.dev/lighthouse-largest-contentful-paint)",
                "score": null,
                "scoreDisplayMode": "informative",
                "displayValue": "1 element found",
                "details": {
                    …
                    "items": [
                        {
                            "node": {
                                "snippet": "<ins id=\"aswift_0_expand\" style=\"…\">",
                                "selector": "div#col-left > div#col-right > ins.adsbygoogle > ins#aswift_0_expand",
                                "nodeLabel": "ins",
                                "path": "1,HTML,1,BODY,1,DIV,5,DIV,0,DIV,0,INS,0,INS",
                                "type": "node"
                            }
                        }
                    ]
                }
            },

Conclusion

LightHouse 6 expose maintenant la statistique CLS très intéressante. Si vous ne vous êtes jamais préoccupé jusqu’ici de la stabilité d’une page, comme l’auteur de cet article, la métrique CLS nous permet de découvrir de nouvelles bonnes pratiques de développement, sachant maintenant à quel point une mauvaise expérience utilisateur peut avoir des conséquences dramatiques en raison de l’instabilité d’une page.

Cela va dépendre de la complexité du site et du code, mais en quelques étapes les pages peuvent être stabilisées :

  • en prédéfinissant dans la mesure du possible les conteneurs, même vides, dans le squelette HTML au lieu de les insérer dynamiquement avec Javascript.
  • en définissant des tailles fixes ou minimales (height, min-height, width, min-width) aux conteneurs.
  • en définissant les propriétés CSS ayant un impact sur les changements de mise en page (overflow…) dans la feuille de styles CSS critique et principale, pas de manière différée.
  • en définissant les dimensions pour les images, videos, iframes, annonces (ads)…

3 outils indispensables pour optimiser les scores CLS :

  • Chrome - Outils de développement onglet LightHouse : rapports.
  • Chrome - Outils de développement onglet Performance : chronologie Experience.
  • Fichiers JSON des résultats LightHouse (lignes de commandes).