wandres.dev
RENDIMIENTO DEL CSS · contain, content-visibility y el coste

content-visibility, contain-intrinsic-size y la búsqueda en la página

Saltarse el renderizado de lo que no se ve sin romper la barra de scroll, la búsqueda con Ctrl+F ni la accesibilidad: los dos valores, su placeholder de tamaño y sus límites reales.

⏱ 17 min

content-visibility: auto es lo más parecido que hay en CSS a una optimización gratuita: el navegador se salta el estilo, el layout y el pintado de todo lo que está fuera de la pantalla, y lo hace sin que tú escribas una línea de JavaScript de virtualización. Las dos preguntas que decide si puedes usarlo son siempre las mismas, y ninguna es de rendimiento: qué pasa con la barra de scroll, y qué pasa cuando el usuario pulsa Ctrl+F.

🎯 Al terminar esta lección sabrás
  • Distinguir auto de hidden y de display: none por lo que conservan.
  • Dimensionar el placeholder con contain-intrinsic-size y su palabra clave auto.
  • Explicar qué le ocurre a la búsqueda en la página y a la accesibilidad en cada valor.
  • Evitar el salto de la barra de scroll y los enlaces internos rotos.

Los dos valores, y qué conservan

content-visibility: auto. El elemento aplica contención de layout, estilo y pintado, y además el navegador se salta el renderizado de su contenido mientras no es relevante para el usuario. Cuando el elemento se acerca a la ventana, el navegador lo renderiza. El contenido sigue en el DOM, sigue en el árbol de accesibilidad, sigue siendo enfocable y sigue encontrándose con la búsqueda de la página.

content-visibility: hidden. El contenido se salta siempre, independientemente de si está a la vista, hasta que tú cambies el valor. No es enfocable, no está en el árbol de accesibilidad y no lo encuentra la búsqueda.

La diferencia con display: none es la que justifica que hidden exista: display: none destruye el estado de renderizado, así que volver a mostrar el elemento cuesta un renderizado completo desde cero. content-visibility: hidden lo conserva en caché, y volver a mostrarlo es mucho más barato. Es la opción correcta para paneles de pestañas y acordeones que se abren y cierran con frecuencia, donde display: none produce un tirón perceptible al reabrir.

auto hidden display: none
Se salta el renderizado fuera de pantalla siempre siempre
En el árbol de accesibilidad no no
Lo encuentra la búsqueda no no
Enfocable con teclado no no
Estado conservado al volver no

El placeholder de tamaño

Aquí está el detalle que hace o rompe la técnica. Si el navegador no renderiza el contenido de un elemento, no sabe cuánto mide. Sin más información lo trata como si estuviera vacío, y todos esos elementos tienen altura cero. El resultado es un documento que parece medir mil píxeles y crece a cincuenta mil según el usuario va bajando, con la barra de scroll saltando en cada paso. Es visualmente peor que no haber optimizado nada.

contain-intrinsic-size es el tamaño que el navegador debe suponer mientras el contenido está saltado:

.seccion {
  content-visibility: auto;
  contain-intrinsic-size: auto 600px;
}

La palabra clave auto delante del valor es la parte importante: significa “recuerda el tamaño real la primera vez que lo rendericé, y usa eso a partir de entonces”. Con ella, el error de estimación solo existe hasta que el usuario pasa por el elemento la primera vez; después, el tamaño es exacto. Sin ella, la estimación es fija para siempre y la barra de scroll nunca deja de mentir.

Para el eje que no varía —normalmente el ancho, que suele ser el del contenedor— se puede dar el par completo:

contain-intrinsic-size: auto 100% auto 480px;

La estimación inicial merece un poco de cuidado. Una estimación demasiado baja produce una barra de scroll que se alarga; una demasiado alta, una que se encoge. Lo que funciona es medir la mediana real de los elementos y usar ese número, no un valor redondo elegido a ojo.

💡
Elige bien la unidad de contención

La granularidad importa. Aplicar content-visibility: auto a cada párrafo de un artículo produce miles de elementos que el navegador debe evaluar para decidir si son relevantes, y el coste de esa evaluación puede superar lo que ahorras. La unidad correcta es un bloque grande y autónomo: una sección, un capítulo, una tarjeta de un catálogo, un mensaje de una conversación. Decenas o cientos de elementos, no decenas de miles.

La búsqueda en la página y la accesibilidad

Esta es la parte que decide si la técnica es aceptable para tu contenido.

Con auto, la especificación es explícita: el contenido saltado tiene que seguir estando disponible para las funciones del agente de usuario, incluida la búsqueda en la página y la navegación con tabulador, y tiene que seguir siendo enfocable. Cuando el usuario busca un texto que está en una sección saltada, el navegador la renderiza y desplaza hasta ella. Igual con el foco: tabular hasta un elemento saltado lo hace relevante y lo renderiza.

Eso es lo que separa a content-visibility: auto de la virtualización con JavaScript, y es una diferencia grande. Una lista virtualizada a mano no tiene en el DOM los elementos que no se ven, así que la búsqueda del navegador no los encuentra, el lector de pantalla no los anuncia, y Ctrl+F da cero resultados en una página que sí contiene la palabra. Ese es un fallo de accesibilidad real y frecuente en aplicaciones que virtualizan listas largas.

Dos matices honestos:

El comportamiento no es idéntico en todos los motores. Hay diferencias documentadas en cómo la búsqueda nativa localiza contenido dentro de subárboles saltados, y conviene comprobarlo en el navegador de tus usuarios en lugar de darlo por hecho a partir de la especificación.

Un elemento con content-visibility: auto no se salta si contiene el foco, una selección del usuario o algo en la capa superior. Es un comportamiento correcto, y explica por qué a veces un elemento que esperabas saltado aparece renderizado en el inspector.

Con hidden, en cambio, el contenido está fuera para todo el mundo: no lo encuentra la búsqueda, no lo lee el lector de pantalla y no se tabula hasta él. Eso es lo correcto para contenido genuinamente oculto —el panel de una pestaña que no está activa— y es incorrecto para contenido que el usuario debería poder encontrar.

Efectos secundarios y ganancia real

Los dos efectos secundarios que hay que prever

Los enlaces internos y el desplazamiento a un ancla. Navegar a #seccion-9 en un documento con secciones saltadas funciona, pero la posición final puede desviarse si la estimación de tamaño de las secciones anteriores era mala. Con contain-intrinsic-size: auto el problema desaparece en cuanto el usuario ha pasado por ellas; en una carga en frío, no. Para documentos donde los enlaces internos son importantes, merece la pena estimar bien.

La contención implícita. content-visibility: auto aplica contención de layout, estilo y pintado. Eso significa que arrastra todos los efectos secundarios descritos en el capítulo sobre contain: el elemento se convierte en bloque contenedor de posicionados, crea contexto de apilamiento y establece un contexto de formato independiente. Si una sección contiene un elemento con position: fixed o un menú que sobresale, se va a comportar de otra forma.

Qué esperar en números

La ganancia se concentra en el tiempo de renderizado inicial y es proporcional a la fracción del documento que está fuera de pantalla. En un documento muy largo —un artículo con cien secciones, un catálogo con mil tarjetas— saltarse el layout y el paint del noventa y cinco por ciento del contenido es una mejora sustancial y fácil de medir en el perfil de la carga.

En un documento que cabe casi entero en dos pantallas, la ganancia es cero o negativa, porque el trabajo de evaluar la relevancia de cada elemento no se compensa con nada. Como todas las técnicas de este nivel, se aplica donde el perfil dice que hay un problema, no por defecto.

Es la primera optimización de renderizado que la plataforma diseñó para no romper la accesibilidad, y eso es lo que la hace distinta

Vale la pena entender qué hace realmente especial a esta propiedad, porque no es el ahorro. La virtualización de listas existe desde hace más de una década y ahorra más: si no metes los elementos en el DOM, no hay nada que estilar, medir ni pintar. Lo que ocurre es que ese ahorro se paga con una factura que casi nadie contabiliza y que recae siempre sobre las mismas personas. Un documento virtualizado miente sobre su propio contenido: Ctrl+F no encuentra lo que sí está en la página, el lector de pantalla anuncia una lista de veinte elementos cuando hay mil, la navegación con teclado se atasca en los bordes, el traductor automático del navegador solo traduce lo visible, y el buscador que rastrea la página sin ejecutar JavaScript no ve nada. Todo eso es invisible para quien construye la función con ratón y vista, y aparece entero para quien no. content-visibility está diseñada al revés: parte de que el contenido existe en el documento y solo negocia cuándo se hace el trabajo de renderizarlo, dejando intactas las capas —accesibilidad, búsqueda, foco, selección— que dependen de que exista. Es más lenta que virtualizar y es la decisión correcta, y el patrón que enseña vale para cualquier optimización que consideres en tu carrera: antes de aceptar un ahorro, pregunta qué capacidad estás retirando y a quién se la retiras. Si la respuesta es “a los usuarios que ya tenían menos”, el ahorro no era tan barato como parecía.