Éviter le rendu hors écran grâce à content-visibility et contain-intrinsic-size
Apprenez comment la visibilité du contenu permet de réduire les coûts liés à la mise en page sur les pages longues générées par serveur, pourquoi la propriété contain-intrinsic-size est indispensable, et comment elle se compare à la virtualisation.
Les pages longues remplies d’éléments répétitifs, tels que des listes de produits, des flux d’actualités, des archives et de grandes tables, semblent souvent lentes, même lorsque leur JavaScript est de petite taille. La raison en est généralement que le navigateur calcule les styles et la mise en page pour chaque élément de la page, y compris des centaines de cartes auxquelles l’utilisateur n’a pas encore déroulé la page et qu’il ne verra peut-être jamais. Ce guide montre comment deux propriétés CSS, content-visibility et contain-intrinsic-size, permettent au navigateur de différer ce travail, comment ce changement concerne les Core Web Vitals et l’indexation, ainsi que dans quels cas cette technique ne fonctionne pas ou n’est pas l’outil adapté.
Un cas typique : une longue liste lente sans raison évidente
Imaginez une page de catégorie « Afficher tous les produits » pour un magasin en ligne. Environ 600 fiches produits, chacune contenant une image, un titre, un prix et une note, sont générées sur le serveur pour former une seule longue page. Il n’y a ni pagination, ni défilement infini, ni virtualisation. L’entreprise préfère cette approche : une seule URL, chaque produit accessible, tout pouvant être retrouvé grâce à la fonction de recherche intégrée au navigateur.
Avec un ordinateur portable de gamme moyenne, il faut près de quatre secondes avant que la page ne devienne interactive. Le principal suspect est JavaScript : un paquet trop volumineux, une fonction useEffect qui ne fonctionne pas correctement, ou un composant qui se réaffiche en boucle. Une analyse approfondie du paquet ne révèle rien qui puisse expliquer ce retard.
Le panneau Performance des DevTools raconte une histoire différente. La majeure partie du temps consacré au thread principal est utilisée pour le Layout, et cela se produit avant même que les scripts n’aient la possibilité d’intervenir.
Pourquoi le contenu hors écran vous coûte encore cher
Le rendu n’est pas une opération unique. Le navigateur résout d’abord les styles de chaque élément, puis exécute le calcul de mise en page pour déterminer la taille et la position de chaque boîte, avant d’enfin dessiner les pixels. Le dessin se limite principalement à ce qui se trouve dans ou près de la zone visible, mais la résolution des styles et le calcul de mise en page s’appliquent à l’ensemble du document, y compris au contenu situé bien en bas de la page. Au moment où le navigateur décide ce qu’il faut dessiner, le travail coûteux de géométrie est déjà terminé.
Dans l’exemple de magasin, cela signifie que les 600 cartes, chacune comportant une case d’image, un titre de présentation, un prix et une ligne d’évaluation, doivent tous subir des traitements de style et de mise en page avant que l’acheteur ne voie la première d’entre elles. L’acheteur ne voit probablement que huit produits. Les 592 autres ne sont pas utiles pour l’instant, mais le navigateur ne sait pas si le visiteur va un jour faire défiler la page vers le bas ; par conséquent, il les traite par défaut comme du contenu qui doit être prêt immédiatement. C’est ce gaspillage qu’il convient d’éliminer. Si vous souhaitez un rappel sur la différence de coût entre ces étapes du processus, consultez notre analyse de ce que coûtent respectivement le reflow, la répeinture et la composition pour le navigateur.
La solution : deux déclarations sur l’élément récurrent
Appliquez ces deux propriétés à l’élément qui se répète, ici la fiche produit. La première indique au navigateur qu’il peut omettre de rendre le contenu de la fiche lorsque celle-ci se trouve loin de la zone visible de l’écran. La seconde définit une taille de remplacement pour les fiches dont la taille réelle n’est pas encore connue.
.product-card {
content-visibility: auto;
contain-intrinsic-size: auto 340px;
}
Avec content-visibility: auto, une fiche qui n’est pas proche de la zone visible reste dans le DOM et dans l’arbre d’accessibilité, et find-in-page parvient toujours à localiser son texte. Ce qui change, c’est que le navigateur n’applique pas de traitements de style, d’agencement ou de rendu sur son contenu tant que la fiche n’approche pas de l’écran. En réalité, cette propriété applique des contraintes d’agencement, de style et de rendu à l’élément, ce qui permet au navigateur de traiter le sous-arbre comme indépendant et de l’omettre en toute sécurité.
Quelle peut être l’ampleur des gains
Dans une démonstration publiée sur web.dev, Google a pris une page longue divisée en sections et a réduit le temps de rendu de 232 ms à 30 ms, ce qui représente une amélioration d’environ sept fois. Il convient de se rappeler qu’il s’agit d’une page de démonstration spécialement conçue et non d’un site en production. Pour une fiche produit réelle comme celle décrite ci-dessus, on peut s’attendre à une amélioration d’environ deux fois, ce qui reste suffisant pour faire la différence entre une page qui continue de charger et une page qui semble prête dès le premier affichage.
Les documents extrêmes montrent les limites possibles. Lors d’un discours au Chrome Dev Summit, cette propriété a été appliquée à une énorme spécification HTML en une seule page comptant plus de 270 000 nœuds DOM, et le temps d’affichage est passé d’environ 50 secondes à environ 400 millisecondes. Peu de pages ressemblent à cela, mais cela illustre l’effort considérable investi dans du contenu que personne ne voit.
Il est utile d’être précis quant à ce qui se passe. Le CSS ne rend pas le processeur plus rapide. Vous indiquez au navigateur qu’une grande partie du travail n’a pas besoin d’être effectuée immédiatement. Lorsqu’une page contient 600 cartes et que la zone de visualisation n’en affiche que huit, rendre toutes les cartes d’abord n’est rarement l’utilisation optimale du thread principal.
Pourquoi contain-intrinsic-size n’est pas optionnel
Si vous utilisez uniquement content-visibility: auto, la barre de défilement commence à sauter au fur et à mesure que vous scrolllez, ce qui peut facilement être confondu avec un bug sans rapport.
La cause est simple : une carte dont le rendu a été sauté n’a pas de hauteur définie, donc elle est dimensionnée comme si elle était vide. Avec des centaines de cartes ainsi sautées, la hauteur totale du document est fortement erronée, et la barre de défilement reflète cette hauteur incorrecte. Lorsque les cartes deviennent visibles et acquièrent leur vraie taille, le document s’agrandit et la molette de défilement se déplace.
contain-intrinsic-size définit la taille à prendre en compte lorsque un élément est sauté. La valeur 340px dans l’exemple correspond à une estimation pour des cartes qui n’ont pas encore été affichées. Aucune précision extrême n’est requise ; une approximation raisonnable suffit pour éviter que la barre de défilement ne bouge visiblement.
Ce que ajoute le mot-clé auto
auto est facile à négliger, mais il a une grande importance. Avec auto, une fois qu’une carte a été affichée, le navigateur enregistre sa taille réelle. Si cette carte quitte ensuite la zone de visualisation et est à nouveau sautée, le navigateur utilise cette taille mémorisée plutôt que votre estimation. Sans auto, chaque carte redevient soumise à l’estimation fixe chaque fois qu’elle sort de la zone de visualisation.
Les cartes affichées à l’écran présentent toujours leur hauteur réelle, quel que soit le cas. La différence apparaît avec du contenu de hauteur variable : un nom de produit long qui s’étend sur une ligne supplémentaire, ou un badge promotionnel qui ajoute une ligne. Sans auto, ces cartes reviennent à la hauteur estimée lorsqu’elles quittent le champ de vision et que la position de défilement change. Utilisez toujours auto.
Support des navigateurs et amélioration progressive
Le support n’est plus réservé uniquement à Chrome. Chrome prend en charge content-visibility depuis la version 85 (2020), Firefox depuis la version 125, et Safari depuis la version 18. Au moment de la rédaction de ce texte, il est classé comme « Baseline Newly Available », un statut atteint le 15 septembre 2025, ce qui signifie qu’il fonctionne sur les trois principaux moteurs de navigateur ; vérifiez les données de compatibilité actuelles si vous prenez en charge des versions plus anciennes.
Les navigateurs qui ne comprennent pas cette propriété l’ignorent simplement et affichent tout comme d’habitude. Cela en fait une amélioration progressive qui ne présente aucun inconvénient pour les anciens clients.
Comment cela se relie aux Core Web Vitals et au SEO
La motivation ici est la performance, mais les pages de catégorie sont précisément le type d’URL que les équipes de recherche surveillent de près. Elles sont publiques, ciblent des requêtes précieuses, et Google évalue leur performance pour les utilisateurs réels.
Le rendu du layout et de l’affichage contribue à deux des trois Core Web Vitals que Google utilise comme indicateur de classement :
- Interaction to Next Paint (INP) en bénéficie directement. L’évitement du rendu des cartes hors écran libère le thread principal, permettant ainsi des réponses plus rapides aux tapotements et clics.
De nombreuses pages non commerciales présentent cette structure, que ce soit des documents et des flux d’actualités, des archives de billets de blog, de longs threads de discussion ou des articles à plusieurs sections. Lorsque de nombreux éléments similaires sont disposés verticalement et que la plupart se trouvent hors écran, et que la page est publique, ce changement affecte les métriques mesurées par Google.
Faites attention à ne pas promettre d’améliorations de classement. L’observation d’un seul site sur quelques semaines ne prouve pas grand-chose, et le classement dépend de bien plus d’une métrique. Ce que vous pouvez raisonnablement attendre, c’est une évolution dans la bonne direction selon les données de terrain, par exemple dans PageSpeed Insights, une fois que suffisamment d’échantillons d’utilisateurs réels auront été collectés.
L’avantage ne se limite pas aux pages publiques. Les tableaux de bord, les tables d’administration et les outils internes subissent le même coût de rendu lié aux 600 lignes. Derrière un processus d’authentification, il n’y a pas d’avantage en termes de recherche, mais l’amélioration de l’expérience utilisateur reste la même, grâce au même CSS.
Le fait d’éviter le rendu cache-t-il du contenu aux robots de recherche ?
C’est la première question qu’un spécialiste SEO méticuleux se posera, et elle est justifiée étant donné le nombre de méthodes de chargement différé qui rendent le contenu invisible pour les bots. La différence clé réside dans le fait que content-visibility: auto est une optimisation de rendu, et non un changement de visibilité. Le contenu existe dans l’HTML et dans le DOM dès que la page se charge ; seul son affichage est différé jusqu’à ce que l’élément approche de la zone visible de l’écran.
Googlebot ne fait pas défiler l’écran comme le ferait une personne. Au lieu de cela, il utilise une zone de visualisation extrêmement grande pour l’affichage, puis examine le DOM qui en résulte. Les cartes que l’on dépasse mais qui sont présentes font partie de ce DOM, de sorte que chaque titre de produit et chaque prix est indexé comme n’importe quel autre contenu.
Contrairement à cela, l’ancienne méthode consistait à insérer du contenu uniquement lorsqu’un événement de défilement se produisait. Les outils d’exploration ne génèrent pas de tels événements, ce qui signifie que ce type de contenu pouvait réellement manquer dans l’index. content-visibility ne peut pas provoquer ce problème, car rien n’est inséré ultérieurement ; tout est déjà présent dès le début.
Une précaution n’est pas liée à la propriété elle-même. Si la page génère son contenu à l’aide de JavaScript côté client, vous rencontrez un problème SEO distinct. Googlebot exécute bien le JavaScript, mais son affichage est mis en file d’attente, ce qui ralentit le processus et diminue sa fiabilité ; de plus, de nombreux autres robots d’indexation gèrent mal le JavaScript. Pour du contenu public, affichez l’HTML sur le serveur et ajoutez ensuite la propriété content-visibility dans le CSS. Cette combinaison permet d’obtenir un contenu indexable ainsi que de meilleures performances.
Comparaison avec le défilement infini, la virtualisation et les observateurs personnalisés
Le défilement infini, les API paginées et la virtualisation visent tous le même problème des pages longues et lentes, il est donc utile de les comparer honnêtement.
Charger davantage d’éléments au fur et à mesure que l’utilisateur défile résout le problème au niveau des données. C’est le choix approprié lorsque l’ensemble des données est vraiment volumineux et ne doit en aucun cas être envoyé intégralement au navigateur.
Cela comporte des coûts. Il faut modifier l’API, gérer les états de chargement, ajouter des écouteurs de défilement et suivre l’état des éléments déjà chargés. L’expérience utilisateur change également : la fonction de recherche dans la page ne peut pas localiser les éléments qui n’ont pas été chargés, et atteindre la fin de la liste devient fastidieux. Sur une page de catégorie publique, cela ajoute une charge SEO supplémentaire, car les produits qui n’apparaissent qu’après défilement ne sont pas visibles par les robots de recherche ; il faut donc des URLs de secours paginées ainsi que du markup supplémentaire pour les rendre indexables. Pour 600 cartes déjà présentes dans l’HTML, cela signifie une réécriture ainsi que des travaux SEO supplémentaires pour résoudre ce qui est en réalité un problème de rendu.
Bibliothèques de virtualisation
La virtualisation, grâce à des bibliothèques comme react-window ou TanStack Virtual, conserve l’ensemble des données en mémoire tout en supprimant les nœuds DOM pour tout ce qui se trouve en dehors de la zone visible. Cette méthode fonctionne, et à très grande échelle, c’est le meilleur choix, comme expliqué ci-dessous.
Le prix dépend de JavaScript, d’une réécriture des composants ainsi que d’un traitement particulièrement complexe pour les lignes de hauteur variable. Comme les éléments supprimés ne se trouvent pas du tout dans le DOM, find-in-page ne peut pas les détecter, les lecteurs d’écran ont du mal avec eux, et sur une page publique, les crawlers les manquent.
Une approche basée sur IntersectionObserver développée manuellement
Rendre les éléments soi-même lorsque IntersectionObserver les indique comme visibles revient à recréer en JavaScript sur le thread principal ce que content-visibility: auto fait déjà, et à réintroduire les problèmes liés à l’ancrage de défilement que le navigateur a déjà résolus nativement. Il y a peu de raisons d’écrire ce code aujourd’hui.
Choix entre ces méthodes
Le véritable avantage de content-visibility n’est pas qu’il surpasse ces techniques, mais plutôt qu’il est bien moins coûteux. Il s’agit d’une seule propriété CSS : pas de JavaScript, aucune modification d’API, pas de réécriture, et le contenu reste dans le DOM pour les recherches, les technologies d’assistance et la fonction « trouver dans la page ».
L’équilibre à trouver doit être clairement défini. content-visibility permet d’économiser des ressources de rendu, mais pas de mémoire. Chaque nœud DOM existe toujours. Avec 600 cartes, cela est négligeable. Mais avec 50 000 ou 100 000 éléments, la taille du DOM devient un problème à part entière, et c’est là que la virtualisation justifie sa complexité. Identifiez d’abord lequel des deux problèmes vous concerne, soit le coût de rendu soit la taille du DOM, avant de choisir l’outil adapté.
Où cela fonctionne et où cela pose des problèmes
Ce n’est pas une propriété à utiliser partout. Elle est particulièrement utile lorsque la même structure se répète de nombreuses fois, par exemple :
- les cartes dans un catalogue ou une liste
Tout élément faisant partie de plusieurs blocs similaires empilés verticalement, dont la plupart sont hors écran au chargement, constitue un bon candidat à tester.
La mesure du contenu sauté donne des résultats erronés
Cette propriété entre en conflit avec du code qui a besoin d’une géométrie précise d’un sous-arbre avant que celui-ci ne soit rendu. Un exemple typique est la lecture de la hauteur d’un élément interne d’une ligne qui se trouve encore hors écran.
const height = row
.querySelector('.details')
.getBoundingClientRect()
.height;
Mesurer le contenu interne d’un élément sauté à l’aide de getBoundingClientRect() avant même qu’il ne soit rendu donne des valeurs nulles ou incorrectes. La boîte propre à l’élément indique la taille du placeholder issue de contain-intrinsic-size, qui peut également ne pas correspondre à la réalité. Un tel code est courant dans les interfaces réelles, par exemple lorsque vous :
- ancrez une aide contextuelle à côté d’un déclencheur
- déterminez les valeurs de début et de fin pour une animation
- décidez de l’endroit où s’ouvre un menu déroulant
- dimensionnez les lignes d’une liste virtuelle
- gérez la logique de positionnement collant
- alignez un composant par rapport à un autre
Si une géométrie précise est importante avant que le contenu ne devienne visible, effectuez des tests approfondis avant d’ajouter cette propriété.
Autres cas peu appropriés
- En-têtes collants, ainsi que des mises en page dont les calculs ne fonctionnent que lorsque tous les enfants possèdent une géométrie réelle en même temps.
- Tout ce qui se trouve au-dessus du pli d’affichage. Ce contenu doit s’afficher immédiatement quel que soit le contexte, donc cette propriété n’apporte aucun avantage et entraîne une légère surcharge de gestion.
- Éléments dont les effets visuels dépassent leur boîte. Comme cette propriété applique une limitation de la zone de peinture, le contenu en surcharge, tel que de grandes ombres ou des popovers positionnés à l’intérieur de la carte, peut être coupé au niveau du bord de cette dernière.
Deux détails moins connus
La valeur cachée
content-visibility: hidden évite le rendu de l’élément de manière similaire à display: none, mais le navigateur conserve en mémoire l’état de rendu de cet élément. Le faire réapparaître est considérablement moins coûteux que de rendre visible un élément avec display: none, car il n’est pas nécessaire de tout refaire à partir de zéro. Cela en fait un outil utile pour les onglets, les menus hors écran et les défileurs virtuels. Contrairement à auto, le contenu placé sous hidden n’est pas accessible via la fonction « trouver dans la page » tant qu’il est caché.
Réagir aux changements d’état de saut
Dès qu’un élément utilisant content-visibility: auto passe d’un état de saut à un état de rendu, le navigateur envoie l’événement contentvisibilityautostatechange. En écoutant cet événement, vous pouvez suspendre des scripts gourmands en ressources, tels que le dessin sur canvas, pour du contenu que le navigateur ne rend pas de toute façon.
Vérifier l’effet sur vos propres pages
Une petite expérience suffit pour montrer la différence. Créez une page de test contenant environ 1 000 cartes ainsi qu’un bouton permettant d’ajouter ou de supprimer content-visibility: auto. Ouvrez les outils de développement, allez dans le panneau Performance, enregistrez un rechargement avec le bouton désactivé, puis enregistrez à nouveau avec celui-ci activé, et comparez les blocs Layout de couleur violette dans les deux enregistrements.
Appliquez ensuite cette même vérification à votre page de production la plus longue. Enregistrez son chargement. Lorsque la partie Layout domine et que l’interactivité apparaît tardivement, ajouter content-visibility: auto aux éléments répétitifs constitue souvent l’amélioration la plus simple et efficace possible : une seule propriété, pas de réécriture, pas de migration vers un autre framework.
Points clés
- Les navigateurs gèrent le style et la mise en page de tout le document ;
content-visibility: autoleur permet de différer ce travail pour les éléments éloignés de la zone visible sans supprimer quoi que ce soit du DOM. - Utilisez toujours en même temps
contain-intrinsic-size: auto <estimate>, sinon la barre de défilement va sauter. - Le contenu reste indexable, recherchable et accessible, ce qui le distingue des méthodes de chargement au défilement et de la virtualisation.
- Cela permet d’économiser du temps de rendu, mais pas de mémoire ; une fois que le DOM lui-même devient trop volumineux, la virtualisation est la solution appropriée.
- Évitez de l’utiliser au-dessus du pli de la page, sur des éléments dont vous mesurez la géométrie avant affichage, ainsi que sur des composants qui s’affichent en dehors de leur propre boîte.