content-visibility : le rendu hors écran sans virtualisation de liste
content-visibility : le rendu hors écran sans virtualisation de liste
Une liste de mille commentaires sous un article se rend visiblement lentement, et le scroll s'y saccade sur un téléphone modeste. La réponse standard en React est la virtualisation – react-window ou @tanstack/react-virtual – qui ne rend dans le DOM que les éléments qui tiennent réellement dans la fenêtre visible, plus une petite marge. Ça fonctionne, mais avec un coût dont on parle rarement au moment de l'adopter : les éléments hors de la fenêtre ne sont pas cachés, ils n'existent pas dans le DOM. Appuyer sur Ctrl+F ne trouvera pas le texte du commentaire #850 tant que vous n'aurez pas défilé manuellement jusqu'à lui. L'impression de la page n'affichera que ce qui était rendu au moment de l'appel à l'impression. La navigation du lecteur d'écran par titres ou repères ignore un contenu qui n'existe physiquement pas dans l'arbre – ce n'est pas un bug de la virtualisation, c'en est une conséquence inévitable.
content-visibility attaque ce même problème de performance d'un angle complètement différent : les éléments restent dans le DOM, toujours, en totalité. Le navigateur se contente de sauter le travail qu'il devrait normalement effectuer – mise en page, peinture, création de couches de composition – tant que l'élément n'est pas suffisamment proche de la zone visible pour que ce travail ait un sens.
Ce que « sauter le travail » signifie exactement
content-visibility: auto sur un élément demande au navigateur de lui appliquer le CSS Containment – exactement le même mécanisme qui, dans l'article sur les Container Queries, permettait d'éviter le bouclage lors de l'interrogation de la taille du conteneur. Ici, le containment sert un autre but : si un élément est hors écran, le navigateur peut supposer en toute sécurité que sa mise en page et sa peinture internes n'affectent rien en dehors de lui-même, il peut donc reporter ce travail entièrement, sans recalculer un seul pixel à l'intérieur.
css
.comment {content-visibility: auto;contain-intrinsic-size: auto 180px;}
contain-intrinsic-size n'est pas un ajout cosmétique – sans lui, un élément sauté se réduirait à une hauteur nulle, car le navigateur n'a aucun moyen de connaître sa taille puisqu'il ne calcule pas la mise en page à l'intérieur. C'est exactement le même problème qu'avec container-type: size sans hauteur explicite – sauf qu'ici la conséquence n'est pas une carte qui disparaît, mais une barre de défilement qui saute, car la hauteur du document change brusquement chaque fois qu'un commentaire de plus entre ou sort du mode sauté. La valeur auto 180px dit deux choses au navigateur en même temps : utilise 180px comme première approximation, avant que l'élément n'ait jamais été réellement rendu, puis mémorise sa taille réelle calculée et utilise cette valeur mémorisée comme placeholder pour chaque saut ultérieur – donc si les commentaires réels ont des hauteurs variables, le navigateur devine avec le temps de plus en plus précisément combien d'espace réserver, au lieu de s'en tenir rigidement à un seul chiffre.
Une astuce que la virtualisation complète ne peut pas vous offrir : « hidden until found »
C'est là que content-visibility fait quelque chose que la virtualisation ne peut pas faire, en principe et pas seulement en pratique. Un élément en mode auto, bien que sauté au rendu, garde son texte physiquement dans le DOM. Le navigateur le sait et traite un tel élément comme « caché mais trouvable » (hidden but matchable) : quand l'utilisateur appuie sur Ctrl+F et tape une expression qui correspond au texte à l'intérieur d'un élément sauté, le navigateur annule temporairement le saut, le rend réellement, met en évidence la correspondance et fait défiler jusqu'à elle – automatiquement, sans une ligne de code de votre part. display: none n'a jamais fait cela (Ctrl+F l'ignore tout simplement), et une liste virtualisée ne peut pas le faire non plus, car le texte que vous cherchez n'est tout simplement pas dans le DOM tant que vous n'avez pas défilé manuellement jusqu'à lui.
Exemple pratique : une liste de commentaires sans une ligne de JS de virtualisation
jsx
const CommentList = ({ comments }) => (<ul className="comment-list">{comments.map((comment) => (<li key={comment.id} className="comment"><p className="comment__author">{comment.author}</p><p className="comment__body">{comment.body}</p></li>))}</ul>);export default CommentList;
css
.comment {content-visibility: auto;contain-intrinsic-size: auto 180px;padding: 12px 0;border-bottom: 1px solid #e5e5e5;}
Le composant entier rend les mille <li> dans le DOM – aucune logique de fenêtre de défilement, aucun calcul des éléments actuellement visibles, aucune bibliothèque. Le navigateur décide lui-même, parmi les mille commentaires, pour lesquels il vaut la peine de calculer la mise en page et la peinture à un instant donné, et lesquels « n'existent » plus au sens du rendu, bien qu'ils existent tous dans le DOM.
Quand ça ne suffit pas – et pourquoi la virtualisation garde tout son sens
content-visibility résout le coût du rendu – mise en page et peinture. Il ne résout pas le coût qui fait souvent le plus mal dans une application React : chacun des mille <li> reste un vrai composant React, qui se monte, s'hydrate, peut avoir ses propres hooks et effets. Sauter le rendu CSS ne fait pas disparaître ces mille instances de composants de l'arbre React, ni ne réduit le coût du JavaScript nécessaire à leur création. Pour des blocs de contenu simples, majoritairement statiques (commentaires, paragraphes d'un long article, lignes de tableau sans interactivité), c'est sans importance – le coût réel se trouvait de toute façon dans la mise en page et la peinture, pas dans la logique du composant. Pour des listes composées de composants lourds et interactifs – chacun avec son propre état, ses abonnements, un rendu coûteux – une vraie virtualisation, qui supprime entièrement les instances inutilisées de l'arbre React, reste parfois le seul moyen de garder de la fluidité.
Pièges et bonnes pratiques
contain-intrinsic-sizeest obligatoire en pratique, pas optionnel. L'omettre entraîne un défilement qui saute et une hauteur de document erronée à chaque passage d'un élément en mode sauté et retour.content-visibility: hiddenn'est pas la même chose queauto.hiddensaute le rendu toujours, indépendamment de la position à l'écran, mais contrairement àdisplay: none, il conserve en cache l'état interne de la mise en page calculée – réafficher l'élément (par exemple en changeant d'onglet) est moins coûteux qu'après undisplay: none, car le navigateur ne recalcule pas tout depuis zéro. Un élément aveccontent-visibility: hiddenest par ailleurs correctement retiré de l'arbre d'accessibilité, tout commedisplay: none– c'est un choix sûr pour des onglets et panneaux fréquemment basculés, pas seulement une astuce de performance.- Ce n'est pas un substitut à la virtualisation pour des listes de composants lourds. Considérez-le comme un premier outil, moins coûteux, pour des listes simples et majoritairement statiques – ne passez à une vraie virtualisation que lorsque le profileur indique réellement un coût côté JS/React, pas seulement de mise en page.
- Vérifiez la compatibilité avant de trop compter sur « hidden but matchable ».
content-visibility: autoseul bénéficie aujourd'hui d'un large support dans tous les principaux moteurs, mais le comportement de « révélation temporaire » lors de Ctrl+F est souvent le plus abouti sous Chromium – traitez-le comme un bonus agréable, pas comme une garantie sur laquelle fonder une exigence d'accessibilité.
Conclusion
Virtualisation et content-visibility résolvent au premier regard le même problème – une longue liste qui se rend trop lentement – mais le font à des niveaux totalement différents. La virtualisation supprime entièrement les éléments du DOM et de l'arbre React, au prix de la perte de Ctrl+F, de l'impression et d'une partie de la navigation d'accessibilité. content-visibility laisse le DOM intact et demande au navigateur de sauter uniquement le travail coûteux de rendu pour ce qui n'est pas visible à un instant donné – en échange, vous obtenez une optimisation moins agressive, mais gratuite, sans rien casser de ce que le navigateur savait déjà faire avec un document complet. Pour la plupart des longues listes raisonnablement simples, c'est le bon premier outil – la virtualisation reste en réserve pour les cas où cela ne suffit vraiment pas.