contain: los cuatro tipos y qué garantiza cada uno
size, layout, style y paint: qué promete el desarrollador y qué se permite el navegador con cada uno, los atajos strict y content, y los efectos secundarios que rompen páginas.
contain no es una propiedad de rendimiento con cuatro intensidades. Son cuatro garantías independientes, cada una con su promesa, su beneficio y su efecto secundario, y se combinan. Usarla bien exige saber exactamente qué estás prometiendo, porque tres de las cuatro cambian el comportamiento visible de la página incluso cuando el rendimiento no importa. Esta lección va valor por valor.
- Enunciar la garantía exacta que aporta cada uno de los cuatro tipos de containment.
- Predecir los efectos secundarios de
layoutypaintsobre posicionamiento, apilamiento y desbordamiento. - Elegir entre
strict,contenty una combinación explícita según el caso. - Reconocer los tres fallos clásicos que produce aplicar
containsin entenderlo.
Los cuatro tipos, uno a uno
contain: size
La promesa. El tamaño del elemento no depende en absoluto de sus descendientes.
Lo que se permite el navegador. Calcular el tamaño de la caja sin haber mirado el contenido. Eso rompe la dependencia de abajo arriba, que es la mitad cara del problema del layout: el motor puede dimensionar y posicionar el elemento y todo lo que viene después de él sin haber hecho el layout de su interior.
El efecto secundario. El elemento se dimensiona como si estuviera vacío. En un div en flujo normal, eso significa altura cero, y su contenido se desborda por debajo, superponiéndose a lo siguiente. Es el fallo número uno con contain: “he puesto contain: strict y ha desaparecido la sección”. No ha desaparecido: mide cero.
Por eso size casi nunca se usa sola. Va acompañada de una dimensión explícita, o de contain-intrinsic-size, que le dice al navegador qué tamaño suponer.
.tarjeta {
contain: size layout paint;
contain-intrinsic-size: auto 320px; /* sin esto, colapsa a 0 de alto */
}
Existe una variante parcial, inline-size, que contiene solo el eje en línea. Es la que usan por dentro las container queries: declarar container-type: inline-size aplica containment de inline-size, de layout y de style. La razón es la del nivel anterior: para poder consultar el ancho de un contenedor sin caer en una dependencia circular, el navegador necesita la garantía de que el contenido no puede alterar ese ancho.
contain: layout
La promesa. El layout interno del elemento no afecta a nada de fuera, y nada de fuera afecta al layout interno más allá del tamaño de la caja.
Lo que se permite el navegador. Tratar el interior como un problema separado. Si algo cambia dentro, no hace falta recalcular a los hermanos ni a los ancestros; si algo cambia fuera, no hace falta reordenar el interior salvo que cambie el tamaño disponible. Es la garantía que corta la propagación lateral y hacia arriba.
Los efectos secundarios, y son cuatro, todos con consecuencias visibles:
- El elemento se convierte en contenedor de bloque para los descendientes con
position: absolutey conposition: fixed. Este es el fallo número dos: un modal conposition: fixeddentro de una tarjeta contenida deja de posicionarse respecto al viewport y pasa a posicionarse respecto a la tarjeta. - Crea un contexto de apilamiento. Los
z-indexde dentro dejan de competir con los de fuera. A menudo es lo que quieres; a veces rompe un dropdown que se dibujaba por encima de la cabecera. - Establece un contexto de formato independiente: los márgenes no se colapsan a través del borde y los flotantes no se escapan.
- El elemento pasa a ser un contenedor de consulta implícito para nada en particular, pero sí actúa como contención a efectos de
overflowde flotantes.
contain: paint
La promesa. Los descendientes no pintan nada fuera de la caja del elemento.
Lo que se permite el navegador. Dos cosas grandes. Primera: si el elemento está fuera de la pantalla, puede saltarse el pintado del subárbol entero, sin comprobar si alguno de sus miles de descendientes asomaba por el borde. Segunda: puede recortar de forma barata, porque sabe que el recorte es correcto.
El efecto secundario. Recorta de verdad. Todo lo que sobresalga —una sombra, un outline de foco, un tooltip posicionado en absoluto, un elemento decorativo con margen negativo— se corta al borde de la caja de padding. Este es el fallo número tres, y aparece siempre en el mismo sitio: los menús desplegables. Además, igual que layout, crea contexto de apilamiento y contenedor de bloque para descendientes absolutos y fijos.
contain: style
La promesa. Los efectos de las propiedades que pueden escapar del subárbol se quedan dentro. En la práctica esto son los contadores y las comillas.
Lo que se permite el navegador. No tener que reevaluar contadores del documento entero cuando cambia el contenido del subárbol. Si un elemento con counter-increment aparece o desaparece dentro de una región contenida, la numeración de fuera no cambia.
El efecto secundario. Si tenías una numeración que atravesaba la frontera, se rompe. Un counter-reset fuera y un counter-increment dentro dejan de comunicarse.
Es la garantía más barata de las cuatro y también la de menor impacto: casi ninguna página tiene un problema de rendimiento causado por contadores. Se incluye en los atajos porque es gratis, no porque resuelva nada.
Los atajos y qué contienen
| Valor | Equivale a | Cuándo |
|---|---|---|
contain: strict |
size layout style paint |
El aislamiento máximo. Exige tamaño explícito o contain-intrinsic-size |
contain: content |
layout style paint |
Aislamiento fuerte sin renunciar al dimensionado automático |
contain: none |
— | Valor inicial |
content es el que se usa en el noventa por ciento de los casos, porque conserva lo que hace útil al CSS —que el contenedor crezca con su contenido— y corta la propagación lateral y el pintado. strict es para cuando de verdad puedes decir de antemano cuánto ocupa el elemento.
Y siempre puedes escribir la combinación exacta. contain: layout sola es perfectamente razonable cuando quieres cortar la propagación pero necesitas que el desbordamiento se siga viendo, porque layout no recorta.
/* Un widget que se actualiza solo y cuyo dropdown debe salirse. */
.widget { contain: layout style; }
/* Una fila de lista de tamano conocido dentro de un scroller. */
.fila {
contain: strict;
contain-intrinsic-size: 100% 48px;
}
/* Una tarjeta de altura variable dentro de una rejilla. */
.tarjeta { contain: content; }
Verificar que la promesa es cierta
Antes de aplicar contain a algo, hay cuatro comprobaciones. Se hacen en dos minutos y ahorran un bug de producción.
Para size. ¿Puedo escribir el tamaño del elemento sin mirar dentro? Si la respuesta es “depende del texto”, no puedes usar size sin contain-intrinsic-size, y aun así tendrás una estimación, no una verdad.
Para paint. ¿Hay algo dentro que se dibuje fuera? Comprueba sombras, outline, tooltips, menús, badges con posición negativa y anillos de foco. El anillo de foco es el que más se olvida y el que más daño hace, porque su recorte es un problema de accesibilidad, no estético.
Para layout. ¿Hay algún descendiente con position: fixed que deba posicionarse respecto al viewport? ¿Hay algún z-index que dependa de competir con elementos de fuera?
Para style. ¿Hay contadores o comillas que crucen la frontera?
Cuando el elemento ya tiene overflow: hidden y un tamaño fijo, contain: strict es casi siempre seguro, porque las tres primeras respuestas ya eran negativas antes de que llegaras tú.
Cuando escribes contain: content en un componente estás haciendo algo que va mucho más allá de un ahorro de milisegundos: estás declarando, de forma que el navegador puede verificar, que ese componente es autónomo. Y en cuanto lo declaras, el navegador te lo hace cumplir. Si alguien mete dentro un tooltip que se salía, se corta y se ve inmediatamente. Si alguien mete un modal con position: fixed, aparece descolocado en la primera prueba. Si alguien mete un contador que dependía de la numeración global, se resetea. Todos esos son bugs que se hubieran colado igual sin contain —el componente ya dependía de su entorno— pero que sin contain no se manifiestan hasta que alguien reutiliza el componente en otra página, seis meses después, y no entiende por qué el menú se abre detrás de la cabecera. Aquí el paralelismo con el resto de la ingeniería es exacto: contain es a un componente de interfaz lo que const es a una variable, lo que un tipo es a un argumento, lo que pure es a una función. En los cuatro casos estás renunciando a una libertad que probablemente no usabas, a cambio de que la máquina detecte automáticamente el día en que alguien empiece a usarla. Y en los cuatro casos el beneficio de rendimiento —que existe, porque el compilador o el motor puede asumir más— acaba siendo el menos importante de los dos. Por eso mi recomendación práctica no es “aplica contain donde tengas un problema de rendimiento”, sino “aplica contain: content a tus componentes reutilizables desde el primer día, cuando todavía no cuesta nada arreglar lo que salte”. Aplicarlo después, sobre un componente que lleva tres años acumulando dependencias silenciosas de su entorno, es una tarde de sorpresas.
Lo que contain no hace
Tres aclaraciones que evitan expectativas equivocadas.
No evita el primer layout. Contener un subárbol no impide que el navegador lo maquete al cargar la página. Lo que evita es tener que rehacerlo cuando cambia algo fuera, y tener que recorrer lo de fuera cuando cambia algo dentro. Si tu problema es el tiempo hasta el primer pintado, contain no es la herramienta: la que sí lo es aparece en content-visibility.
No reduce el número de nodos. Un documento con cien mil elementos sigue teniendo cien mil objetos de DOM, cien mil estilos calculados y el consumo de memoria correspondiente, esté contenido o no. Para eso hace falta no crear los nodos.
No es will-change. will-change es una pista sobre el futuro que puede provocar la creación de una capa de composición; contain es una restricción semántica sobre el presente. Ni se sustituyen ni se parecen, y usar will-change de forma indiscriminada consume memoria de GPU sin dar ninguna garantía al motor de layout.
- Coge un componente de tu aplicación y aplícale
contain: strictsincontain-intrinsic-size. Observa el colapso a cero y explica por qué ocurre exactamente. - Aplica
contain: painta una tarjeta que tenga sombra y anillo de foco. Documenta qué se recorta. - Mete un elemento con
position: fixeddentro de un contenedor concontain: layouty comprueba respecto a qué se posiciona. - Construye una lista numerada con
counter-incrementque cruce un contenedor concontain: styley observa la numeración. - Elige el valor mínimo de
containque resuelve tu caso sin ningún efecto secundario, y justifica por escrito por qué cada garantía que incluiste es cierta.