Core Web Vitals : Largest Contentful Paint (LCP)

Le Largest Contentful Paint (LCP) fait partie des Core Web Vitals de Google, les métriques essentielles pour l’expérience utilisateur. Il permet d’évaluer la perception de vitesse en indiquant le moment où le contenu principal de la page s’affiche. En termes, d’UX, un bon LCP contribue à rassurer sur le fait que le contenu de la page est pertinent.

Comment mesurer la vitesse à laquelle le contenu principal d’une page s’affiche sur un navigateur ? C’est un sujet de questionnement pour les développeurs web…


Des métriques anciennes telles que load ou DOMContentLoaded ne suffisent pas car elles ne traduisent pas ce que l’internaute voit sur son écran (NdT : nous abordons régulièrement les évolutions des métriques webperf dans notre newsletter mensuelle, vous pouvez vous y abonner ici).

Par ailleurs, les mesures de performance plus récentes et centrées utilisateur comme le First Contentful Paint (FCP) ne capturent que le tout début du chargement. De ce fait, si une page affiche un splash screen ou un indicateur de chargement, le FCP ne représentera pas réellement ce que voit l’utilisateur au cours de la navigation.

Par le passé, Google a recommandé le First Meaningful Paint et le Speed Index (disponibles dans Lighthouse), mais ils ne permettent pas non plus d’identifier à quel moment le contenu principal est chargé.

Après des recherches et des discussions avec le W3C, Google a convenu du fait que le meilleur moyen d’évaluer le moment auquel le contenu principal d’une page s’affiche, c’est d’observer le moment auquel le plus grand élément de la page est rendu. C’est le Largest Contentful Paint.

Qu'est-ce que LCP ? Définition.

La métrique LCP (Largest Contentful Paint) indique le temps de rendu du plus grand élément de contenu visible dans le viewport.

 

Télécharger le dossier Core Web Vitals

Qu'est-ce qu'un bon Largest Contentful Paint ?

Pour une bonne expérience utilisateur, un site web doit faire en sorte que le Largest Contentful Paint arrive dans les 2,5 premières secondes après le début du chargement de la page.
Mais comment savoir si cet objectif est atteint pour la plupart des utilisateurs ? Le meilleur moyen est de vous référer au 75ème centile des temps de chargement de vos pages, pour les appareils mobiles et desktop, selon les recommandations de Google.

Quels sont les éléments pris en compte pour le calcul du Largest Contentful Paint ?

Comme précisé dans l’API Largest Contentful Paint, les éléments pris en compte pour le calcul du LCP sont les suivants :

  • éléments <img> 
  • éléments <image> dans un élément <svg> 
  • éléments <video> (vignette utilisée)
  • éléments avec une image de fond chargée via la fonction url() (par opposition à un CSS gradient)
  • éléments Block-level contenant des text nodes ou des éléments texte inlinés dépendants.

NB : Google restreint intentionnellement les éléments pris en compte dans le calcul du LCP pour favoriser la simplicité. Des éléments seront ajoutés au fur et à mesure, tels que <svg>, <video>...

Comment est déterminée la taille d'un élément pour mesurer le LCP ? 

La taille de l'élément signalé pour le LCP est généralement la taille visible par l'utilisateur dans le viewport. Si l'élément s'étend à l'extérieur de la fenêtre, ou si l'un des éléments est tronqué ou déborde (overflow), ces parties ne comptent pas.

Pour les éléments image redimensionnés à partir de leur taille intrinsèque (Intrisic Size), la taille qui est indiquée est soit la taille visible, soit la taille intrinsèque, c’est la plus petite des deux qui sera retenue.

Par exemple :

  • pour les images réduites à une taille beaucoup plus petite que leur taille intrinsèque, c’est la taille à laquelle elles sont affichées qui est prise en compte ;
  • pour les images qui sont étirées ou développées dans une taille plus grande, la taille intrinsèque est prise en compte.

Pour les éléments texte, seule la taille de leurs text node est prise en compte (le plus petit rectangle qui comprend tous les text nodes).

Pour tous les éléments, aucun margin, padding ou border appliqué via CSS n'est pris en compte.

NB : Déterminer quels text nodes appartiennent à quels éléments peut être compliqué, en particulier pour les éléments dont les dépendances incluent des éléments inlinés et des text nodes bruts, mais aussi des éléments de niveau bloc. Le point à retenir est que chaque text node appartient (et uniquement à) son élément parent au niveau du bloc le plus proche. Selon les spécifications : chaque text node appartient à l'élément qui génère son bloc conteneur.

Quand la plus grande image d’une page est-elle prise en compte ?

Les pages web se chargent souvent par étapes et par conséquent, il est possible que le plus grand élément de la page varie.

Pour gérer ce phénomène, le navigateur envoie un PerformanceEntry de type largest-contentful-paint, identifiant le plus grand élément de la page dès que le navigateur affiche la première image. Puis, après le rendu des images suivantes, il génère un nouveau PerformanceEntry à chaque fois que le plus grand élément de contenu change.

Sachez qu'un élément ne peut être considéré comme le plus grand qu'une fois rendu et visible par l'utilisateur.

Ainsi, les images qui n'ont pas encore été chargées ne sont pas considérées comme "rendues", tout comme les text nodes qui n'utilisent pas de fonts pendant la période de font block. Dans de tels cas, un élément plus petit peut être signalé comme le plus grand, mais dès que le rendu du plus grand élément est fini, il sera signalé via un autre objet PerformanceEntry.

En plus des images et des polices dont le chargement arrive plus tard, une page peut ajouter de nouveaux éléments au DOM à mesure que de nouveaux contenus deviennent disponibles. Si l'un de ces nouveaux éléments est plus volumineux que le précédent élément de contenu le plus grand, un nouveau PerformanceEntry sera également signalé.

Si une page supprime un élément du DOM, cet élément ne sera plus pris en compte. De même, si la ressource image associée à un élément change (par exemple, changement de img.src via JavaScript), alors cet élément sera interrompu jusqu'à ce que la nouvelle image se charge.

Le navigateur cesse de signaler de nouveaux inputs dès que l'utilisateur interagit avec la page (toucher, défilement ou pression sur une touche), car l'interaction change souvent ce qui est visible pour l'utilisateur (particulièrement en cas de scroll).

En termes d’analytics, vous ne devez signaler que le dernier PerformanceEntry envoyé à votre outil d'analyse.

NB : Étant donné que les utilisateurs peuvent ouvrir des pages dans un onglet en arrière-plan, il est possible que le Largest Contentful Paint ne se produise pas avant que l'utilisateur ne revienne sur un onglet donné, et donc arriver beaucoup plus tard que lors du premier chargement.

Différence entre temps de chargement et temps de rendu 

Pour des raisons de sécurité, l'horodatage du rendu des images n'est pas exposé pour les images cross-origin qui n'ont pas l'en-tête Timing-Allow-Origin. Seul leur temps de chargement est exposé (car il l’est déjà via de nombreuses autres API web).

L'exemple que nous verrons ci-dessous (dans “Mesurer le LCP en JavaScript”) montre comment gérer les éléments dont le temps de rendu n'est pas disponible. Mais si c’est possible, il est toujours recommandé de définir l'en-tête  Timing-Allow-Origin de façon à ce que vos mesures soient plus précises.

LCP : Comment la disposition des éléments et les changements de taille sont-ils gérés ? 

Pour limiter les coûts de calcul, les modifications apportées à la taille ou à la position d'un élément ne génèrent pas de nouveaux “candidats” en tant qu’éléments les plus importants de la page. Seules la taille et la position initiales de l'élément dans le viewport sont prises en compte.

Cela signifie que les images qui sont initialement rendues hors viewport et qu’y s’y retrouvent ensuite peuvent ne pas être signalées. Par ailleurs, les éléments initialement rendus dans le viewport qui sont ensuite poussés vers le bas, hors de la vue, continueront d’être pris en compte avec leur taille initiale.

Cependant, (comme mentionné ci-dessus), un élément peut ne plus être pris en compte s'il est supprimé du DOM ou si sa ressource image associée change.

Exemples de Largest Contentful Paint

Voici quelques exemples de moments où le Largest Contentful Paint se produit :

Core Web Vitals Google : Largest Contentful Paint (1)

Core Web Vitals Google : Largest Contentful Paint (2)

Dans les deux timelines ci-dessus, le plus grand élément change à mesure que le contenu se charge. Dans le premier exemple, un nouveau contenu est ajouté au DOM, ce qui change l’élément considéré comme le plus grand. Dans le deuxième exemple, la mise en page change et le contenu qui était auparavant le plus grand est supprimé de la fenêtre.

Bien que le contenu chargé plus tard sur une page soit souvent le plus volumineux, ce n'est pas toujours le cas. On le voit dans les deux exemples suivants qui montrent que le plus grand contenu est visible avant le chargement complet de la page :

Core Web Vitals Google : Largest Contentful Paint (LCP) logo Instagram

Core Web Vitals Google : Largest Contentful Paint (LCP) zone de texte

Dans le premier exemple, le logo Instagram est chargé relativement tôt et il reste l'élément le plus important même si d'autres contenus sont progressivement affichés. 

Dans l'exemple de page de résultats de recherche Google, le plus grand élément est un paragraphe de texte qui s'affiche avant le chargement des images ou du logo. Étant donné que toutes les images sont plus petites que ce paragraphe, ce bloc de texte reste le plus grand élément tout au long du processus de chargement.

NB : Dans la première image de la timeline Instagram, vous remarquerez peut-être que le logo de la caméra n'est pas surligné en vert. En effet, il s'agit d'un élément <svg> et les éléments <svg> ne sont pas actuellement considérés comme des éléments potentiellement les plus grands pris en compte pour mesurer le LCP. Le premier “candidat” au LCP est donc le texte du deuxième cadre.

Comment mesurer le Largest Contentful Paint ?

Le LCP peut être mesuré par les outils Lab ou Field :

Les outils Field (Real User Monitoring)

Les outils Lab (Synthetic Monitoring)

Mesurer le LCP en JavaScript 

La façon la plus simple de mesurer LCP (comme toutes les métriques Field des Web Vitals) est avec web-vitals JavaScript library, qui simplifie la mesure manuelle du LCP en une seule fonction:

import {getLCP} from 'web-vitals';
// Measure and log the current LCP value,
// any time it's ready to be reported.
getLCP(console.log);

Pour mesurer manuellement LCP, vous pouvez utiliser l’API Largest Contentful Paint. L'exemple suivant montre comment créer un PerformanceObserver qui suit les enregistrements et logs des éléments les plus volumineux, et reporte la valeur LCP dans la console :

// Keep track of whether (and when) the page was first hidden, see:
// https://github.com/w3c/page-visibility/issues/29
// NOTE: ideally this check would be performed in the document <head>
// to avoid cases where the visibility state changes before this code runs.
let firstHiddenTime = document.visibilityState === 'hidden' ? 0 : Infinity;
document.addEventListener('visibilitychange', (event) => {
 firstHiddenTime = Math.min(firstHiddenTime, event.timeStamp);
}, {once: true});
// Sends the passed data to an analytics endpoint. This code
// uses `/analytics`; you can replace it with your own URL.
function sendToAnalytics(data) {
 const body = JSON.stringify(data);
 // Use `navigator.sendBeacon()` if available, falling back to `fetch()`.
 (navigator.sendBeacon && navigator.sendBeacon('/analytics', body)) ||
     fetch('/analytics', {body, method: 'POST', keepalive: true});
}
// Use a try/catch instead of feature detecting `largest-contentful-paint`
// support, since some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
 // Create a variable to hold the latest LCP value (since it can change).
 let lcp;
 function updateLCP(entry) {
   // Only include an LCP entry if the page wasn't hidden prior to
   // the entry being dispatched. This typically happens when a page is
   // loaded in a background tab.
   if (entry.startTime < firstHiddenTime) {
     // NOTE: the `startTime` value is a getter that returns the entry's
     // `renderTime` value, if available, or its `loadTime` value otherwise.
     // The `renderTime` value may not be available if the element is an image
     // that's loaded cross-origin without the `Timing-Allow-Origin` header.
     lcp = entry.startTime;
   }
 }
	
 // Create a PerformanceObserver that calls `updateLCP` for each entry.
 const po = new PerformanceObserver((entryList) => {
   entryList.getEntries().forEach(updateLCP);
 });
 // Observe entries of type `largest-contentful-paint`, including buffered entries,
 // i.e. entries that occurred before calling `observe()` below.
 po.observe({
   type: 'largest-contentful-paint',
   buffered: true,
 });
	
 // Log the final LCP score once the
 // page's lifecycle state changes to hidden.
 addEventListener('visibilitychange', function fn(event) {
   if (document.visibilityState === 'hidden') {
     removeEventListener('visibilitychange', fn, true);
     // Force any pending records to be dispatched.
     po.takeRecords().forEach(updateLCP);
     // If LCP is set, report it to an analytics endpoint.
     if (lcp) {
       sendToAnalytics({lcp});
     }
   }
 }, true);
} catch (e) {
 // Do nothing if the browser doesn't support this API.
}

NB : Le LCP ne doit pas être reporté si la page a été chargée dans un onglet d'arrière-plan. Le code ci-dessus résout partiellement ce problème, mais il n'est pas parfait car la page aurait pu être masquée puis affichée avant l'exécution de ce code. Une solution est en cours de discussion sur Page Visibility API spec.

Pourquoi le Largest Contentful Paint est différent entre les données Lab et Field sur PageSpeed Insights ou Lighthouse ?

Page Speed Insights - Difference LCP - FIeld & Lab data

Les différents éléments LCP

L'élément LCP identifié pour les données Lab peut ne pas être le même que celui que les utilisateurs voient lorsqu'ils visitent votre page. Si vous exécutez un rapport Lighthouse pour une page donnée, vous obtiendrez le même élément LCP à chaque fois. Mais si vous examinez les données de terrain (Field) pour la même page, vous trouverez généralement une variété d'éléments LCP différents, qui dépendent des circonstances spécifiques à chaque visite de page.

Par exemple, les facteurs suivants peuvent tous contribuer à la détermination d'un élément LCP différent pour la même page :

  • Les différentes tailles d'écran des appareils font que différents éléments sont visibles dans la fenêtre d'affichage.
  • Si l'utilisateur est connecté, ou si un contenu personnalisé est affiché d'une manière ou d'une autre, l'élément LCP peut être très différent d'un utilisateur à l'autre.
  • Comme pour le point précédent, si un test A/B est en cours sur la page, des éléments très différents peuvent s'afficher.
  • Le jeu de polices installé sur le système de l'utilisateur peut affecter la taille du texte sur la page (et donc quel élément est l'élément LCP).
  • Les tests de laboratoire sont généralement effectués sur l'URL "de base" d'une page, sans ajout de paramètres de requête ou de fragments de hachage. Mais dans le monde réel, les utilisateurs partagent souvent des URL contenant un identifiant de fragment ou un fragment de texte, de sorte que l'élément LCP peut en fait se trouver au milieu ou en bas de la page (plutôt qu'au-dessus de la ligne de flottaison).

Etant donné que le LCP dans les données Field est calculé d'après le 75ème percentile de toutes les visites sur une page, si un grand pourcentage de ces utilisateurs a un élément LCP qui se charge très rapidement - par exemple un paragraphe de texte rendu avec une police système -, même si certains de ces utilisateurs ont une grande image qui se charge lentement comme élément LCP, cela peut ne pas affecter le score de cette page à condition que cela arrive à moins de 25 % des visiteurs.

L'inverse peut également être vrai. Avec des données Lab, on pourrait identifier un bloc de texte comme l'élément LCP parce qu'il émule un téléphone Moto G4 dont la fenêtre d'affichage est relativement petite, et que l'image principale de votre page est initialement rendue hors écran. Vos données de terrain (Field), cependant, peuvent inclure principalement des utilisateurs de Pixel XL avec des écrans plus grands, donc pour eux cette image principale qui se charge lentement est leur élément LCP.

L'effet de l'état du cache sur le LCP

Les données de laboratoire correspondent généralement à une page avec un cache "froid", mais lorsque les utilisateurs réels visitent cette page, il se peut que certaines de ses ressources soient déjà mises en cache.

La première fois qu'un utilisateur charge une page, elle peut se charger lentement, mais si la page est correctement configurée en cache, la prochaine fois que cet utilisateur reviendra, la page se chargera plus vite.

Bien que certains outils Lab permettent d'exécuter plusieurs fois la même page (pour simuler l'expérience des visiteurs qui reviennent), il n'est pas possible pour un outil Lab de savoir quel pourcentage des visites dans le monde réel provient de nouveaux utilisateurs par rapport à ceux qui reviennent.

Les sites avec des configurations de cache bien optimisées et beaucoup de visiteurs réguliers peuvent découvrir que leur LCP dans le monde réel est beaucoup plus rapide que ce qu'indiquent les données de laboratoire.

Les optimisations d'AMP ou de Signed Exchange

Les sites AMP ou qui utilisent Signed Exchange (SXG) peuvent être préchargés par des agrégateurs de contenu comme Google Search. Cela peut se traduire par une vitesse de chargement nettement meilleure pour les utilisateurs qui visitent vos pages depuis ces plateformes.

Outre le préchargement inter-origine, les sites eux-mêmes peuvent précharger du contenu pour les pages suivantes sur leur site, ce qui pourrait améliorer le LCP pour ces pages également.

Les outils Lab ne simulent pas les gains obtenus grâce à ces optimisations, et même s'ils le faisaient, ils ne pourraient pas savoir quel pourcentage de votre trafic provient de plateformes comme Google Search par rapport à d'autres sources.

L'effet du bfcache sur le LCP

Lorsque les pages sont restaurées à partir du bfcache, l'expérience de chargement est quasi instantanée, et ces expériences sont incluses dans vos données Field.

Les données Lab ne prennent pas en compte le bfcache, donc si vos pages sont adaptées au bfcache, cela se traduira probablement par des scores LCP plus rapides dans les données Field.

L'effet des interactions des utilisateurs sur le LCP

Le LCP identifie le temps de rendu de la plus grande image ou du plus grand bloc de texte dans la fenêtre d'affichage, mais ce plus grand élément peut changer au fur et à mesure que la page est chargée ou si un nouveau contenu est ajouté dynamiquement à la fenêtre d'affichage.

Pour des données de laboratoire, le navigateur attendra que la page soit entièrement chargée avant de déterminer quel était l'élément LCP. Mais pour des données recueillies auprès d'utilisateurs réels, le navigateur cessera de surveiller les éléments plus grands après que l'utilisateur aura fait défiler la page ou interagi avec elle.

C'est logique (et nécessaire) car les utilisateurs attendent généralement pour interagir avec une page qu'elle "semble" chargée, ce qui est exactement ce que la mesure LCP vise à détecter. Il serait également absurde de prendre en compte les éléments ajoutés à la fenêtre d'affichage après l'interaction de l'utilisateur, car ces éléments pourraient n'avoir été chargés qu'en raison d'une action de l'utilisateur.

La conséquence est que les données Field pour une page peuvent rapporter des temps LCP plus rapides, selon la façon dont les utilisateurs se comportent sur cette page.

Field ou Lab : quelles métriques prioriser pour optimiser le LCP ?

En règle générale, si vous disposez à la fois de données de terrain et de données de laboratoire pour une page web, les données de terrain sont celles que vous devez utiliser pour prioriser vos efforts. Puisque les données de terrain représentent ce que vivent les utilisateurs réels, c'est le moyen le plus précis de comprendre réellement ce qui pose problème à vos utilisateurs et ce qui doit être amélioré.

En revanche, si vos données de terrain montrent de bons résultats dans l'ensemble, mais que vos données de laboratoire suggèrent qu'il y a encore de la place pour des améliorations, cela vaut la peine de creuser pour comprendre quelles optimisations supplémentaires peuvent être apportées.

Comment faire si l’élément mesuré par le LCP n’est pas le plus important ?

Dans certains cas, le ou les éléments les plus importants ne sont pas les plus grands, et il peut être plus intéressant de mesurer le rendu de ces éléments les plus importants. C’est possible grâce à Element Timing API, en faisant appel à des Custom Metrics.

Comment améliorer le Largest Contentful Paint

Le LCP peut être dégradé par plusieurs facteurs :

  • des temps de réponse serveur longs,
  • du JavaScript ou des CSS qui bloquent le rendu,
  • le temps de chargement des ressources,
  • le rendu côté client.

Voici en détail les techniques pour améliorer le LCP.
Google propose aussi des techniques additionnelles pour améliorer le LCP (en anglais) :

Suivre les évolutions du Largest Contentful Paint

Google corrige et fait évoluer les API utilisées pour mesurer les métriques, ainsi que la définition des métriques elles-mêmes. Ces modifications peuvent apparaître comme des améliorations ou des régressions dans vos rapports et tableaux de bord internes. Pour vous aider à suivre ces évolutions, les modifications sont consignées dans ce CHANGELOG.

Vous souhaitez creuser le sujet en vidéo ? Le replay de notre webinaire dédié au LCP est ici :

Vous souhaitez savoir comment automatiser les optimisations
qui vous permettront d’améliorer votre LCP et l'ensemble de vos Core Web Vitals ?


Demandez une démo

 

L’article original en anglais est publié sur Web.dev

A lire aussi :


Hello SMX Paris !