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.

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.

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.

FID, CLS, LCP... Vous souhaitez comprendre ce que sont les Core Web Vitals, comment les monitorer et les optimiser, et quels impacts pourraient avoir ces KPI sur votre ranking ?
Suivez notre webinaire en partenariat avec l'agence SEO Clustaar :

 

Vous souhaitez savoir comment automatiser les optimisations
qui vous permettront d’améliorer votre LCP ?


Demandez une démo

 

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


Hello SMX Paris !