Profundicemos en la propiedad de contenido de CSS
En comparación con el pasado, los navegadores modernos se han vuelto realmente eficientes a la hora de representar la red enredada de códigos HTML, CSS y JavaScript que proporciona una página web típica. Se necesitan apenas milisegundos para convertir el código que le damos en algo que la gente pueda usar.
¿Qué podríamos hacer nosotros, como desarrolladores front-end, para ayudar al navegador a ser aún más rápido en el procesamiento? Existen las mejores prácticas habituales que son muy fáciles de olvidar con nuestras herramientas modernas, especialmente en los casos en los que es posible que no tengamos tanto control sobre el código generado. Podríamos mantener nuestro CSS bajo control, por ejemplo, con menos selectores y más simples. Podríamos mantener nuestro HTML bajo control; Mantenga el árbol más plano con menos nodos y, especialmente, menos hijos. Podríamos mantener nuestro JavaScript bajo control; teniendo cuidado con nuestras manipulaciones HTML y CSS.
En realidad, los marcos modernos como Vue y React ayudan bastante con esa última parte.
Me gustaría explorar una propiedad CSS que podríamos usar para ayudar al navegador a determinar qué cálculos puede reducir en prioridad o incluso omitirlos por completo.
Esta propiedad se llama contain
. Así es como MDN define esta propiedad:
La
contain
propiedad CSS permite a un autor indicar que un elemento y su contenido son, en la medida de lo posible, independientes del resto del árbol del documento. Esto permite al navegador recalcular el diseño, el estilo, la pintura, el tamaño o cualquier combinación de ellos para un área limitada del DOM y no para toda la página, lo que genera beneficios de rendimiento obvios.
Una forma sencilla de ver lo que proporciona esta propiedad es que podemos dar pistas al navegador sobre las relaciones de los distintos elementos de la página. No necesariamente elementos más pequeños, como párrafos o enlaces, sino grupos más grandes, como secciones o artículos. Básicamente, estamos hablando de elementos contenedores que contienen contenido, incluso contenido que puede ser de naturaleza dinámica. Piense en un SPA típico donde se inserta y elimina contenido dinámico en toda la página, a menudo independientemente del resto del contenido de la página.
Un navegador no puede predecir el futuro de los cambios de diseño de la página web que pueden ocurrir cuando JavaScript inserta y elimina contenido en la página. Incluso cosas simples como insertar un nombre de clase en un elemento, animar un elemento DOM o simplemente obtener las dimensiones de un elemento pueden provocar un reflujo y un repintado de la página. Estas cosas pueden ser costosas y deben evitarse, o al menos reducirse tanto como sea posible.
Los desarrolladores pueden predecir el futuro porque sabrán sobre posibles cambios futuros basados en la UX del diseño de la página, como cuando el usuario hace clic en un botón, solicitará que se inserten datos en un div ubicado en algún lugar del actual. vista. Sabemos que es una posibilidad, pero el navegador no. También sabemos que existe una clara posibilidad de que insertar datos en ese div no cambie nada visualmente ni de otro modo para otros elementos de la página.
Los desarrolladores de navegadores han dedicado una buena cantidad de tiempo a optimizar el navegador para manejar este tipo de situaciones. Hay varias formas de ayudar al navegador a ser más eficiente en tales situaciones, pero serían útiles sugerencias más directas. La propiedad de contención nos brinda una manera de proporcionar estas sugerencias.
Las diversas formas de contener
La contain
propiedad tiene tres valores que se pueden usar individualmente o en combinación entre sí: size
, layout
y paint
. También tiene dos valores abreviados para combinaciones comunes: strict
y content
. Cubramos los conceptos básicos de cada uno.
Tenga en cuenta que hay una serie de reglas y casos extremos para cada uno de ellos que se tratan en la especificación . Me imagino que esto no será motivo de mucha preocupación en la mayoría de las situaciones. Sin embargo, si obtiene un resultado no deseado, puede resultar útil echar un vistazo rápido a las especificaciones.
También hay un style
tipo de contención en la especificación que este artículo no cubrirá. La razón es que el style
tipo de contención se considera de poco valor en este momento y actualmente corre el riesgo de ser eliminado de la especificación.
Contención de tamaño
size
La contención es realmente sencilla de explicar. Cuando un contenedor con esta contención participa en los cálculos de diseño, el navegador puede omitir bastante porque ignora los elementos secundarios de ese contenedor. Se espera que el contenedor tenga una altura y un ancho establecidos; de lo contrario, colapsa y eso es lo único que se considera en el diseño de la página. Se trata como si no tuviera contenido alguno.
Considere que los descendientes pueden afectar su contenedor en términos de tamaño, dependiendo de los estilos del contenedor. Esto debe tenerse en cuenta al calcular el diseño; con size
la contención, lo más probable es que no se considere. Una vez resuelto el tamaño del contenedor en relación con la página, se calculará el diseño de sus descendientes.
size
la contención realmente no proporciona mucho en cuanto a optimizaciones. Generalmente se combina con uno de los otros valores.
Sin embargo, un beneficio que podría proporcionar es ayudar con JavaScript que altera los descendientes del contenedor según el tamaño del contenedor, como una situación de tipo de consulta de contenedor. En algunas circunstancias, alterar los descendientes según el tamaño del contenedor puede hacer que el contenedor cambie de tamaño después de que se realizó el cambio en los descendientes. Dado que un cambio en el tamaño del contenedor puede desencadenar otro cambio en los descendientes, podría terminar con un bucle de cambios. size
la contención puede ayudar a prevenir ese bucle.
Aquí hay un ejemplo totalmente artificial de este concepto de bucle de cambio de tamaño:
En este ejemplo, al hacer clic en el botón de inicio, el cuadro rojo comenzará a crecer, según el tamaño del cuadro principal violeta, más cinco píxeles. A medida que el cuadro morado se ajusta de tamaño, un observador de cambio de tamaño le indica al cuadrado rojo que vuelva a cambiar de tamaño según el tamaño del padre. Esto hace que el padre cambie de tamaño nuevamente y así sucesivamente. El código detiene este proceso una vez que el padre supera los 300 píxeles para evitar el bucle infinito.
El botón de reinicio, por supuesto, vuelve a poner todo en su lugar.
Al hacer clic en la casilla de verificación “establecer contención de tamaño”, se establecen diferentes dimensiones y la size
contención en el cuadro morado. Ahora, cuando haga clic en el botón de inicio, el cuadro rojo cambiará de tamaño según el ancho del cuadro morado. Es cierto que desborda al padre, pero el punto es que solo cambia el tamaño una vez y se detiene; ya no hay un bucle.
Si hace clic en el botón para cambiar el tamaño del contenedor, el cuadro morado se hará más ancho. Después del retraso, el cuadro rojo cambiará de tamaño en consecuencia. Al hacer clic nuevamente en el botón, el cuadro morado vuelve a su tamaño original y luego el cuadro rojo cambiará de tamaño nuevamente.
Si bien es posible lograr este comportamiento sin el uso de la contención, se perderá los beneficios. Si esta es una situación que puede ocurrir con frecuencia en la página, la contención ayuda con los cálculos del diseño de la página. Cuando los descendientes cambian internamente a la contención, el resto de la página se comporta como si los cambios nunca hubieran ocurrido.
Contención de diseño
layout
La contención le dice al navegador que los elementos externos no afectan el diseño interno del elemento contenedor, ni el diseño interno del elemento contenedor afecta a los elementos externos. Entonces, cuando el navegador realiza cálculos de diseño, puede asumir que los diversos elementos que contienen el diseño no afectarán a otros elementos. Esto puede reducir la cantidad de cálculos que deben realizarse.
Otro beneficio es que los cálculos relacionados podrían retrasarse o reducirse su prioridad si el contenedor está fuera de la pantalla o está oscurecido. Un ejemplo que proporciona la especificación es:
[…] por ejemplo, si el cuadro contenedor está cerca del final de un contenedor de bloques y estás viendo el comienzo del contenedor de bloques
El contenedor con layout
contención se convierte en una caja contenedora para absolute
o fixed
posicionar a los descendientes. Esto sería lo mismo que aplicar una relative
posición al contenedor. Por lo tanto, tenga en cuenta cómo los descendientes del contenedor pueden verse afectados al aplicar este tipo de contención.
En una nota similar, el contenedor obtiene un nuevo contexto de apilamiento, por lo que z-index
se puede usar igual que si se aplicara una posición relative
, absolute
o . fixed
Aunque establecer las propiedades top
, right
, bottom
o left
no tiene ningún efecto en el contenedor.
Aquí hay un ejemplo simple de esto:
Haga clic en el cuadro y layout
se alterna la contención. Cuando layout
se aplica la contención, las dos líneas violetas, que están en posición absoluta, se desplazarán hacia el interior del cuadro violeta. Esto se debe a que el cuadro morado se convierte en un bloque contenedor con la contención. Otra cosa a tener en cuenta es que el contenedor ahora está apilado encima de las líneas verdes. Esto se debe a que el contenedor ahora tiene un nuevo contexto de apilamiento y sigue esas reglas en consecuencia.
Contención de pintura
paint
La contención le dice al navegador que ninguno de los elementos secundarios del contenedor se pintará nunca fuera de los límites de las dimensiones de la caja del contenedor. Esto es similar a colocarlo overflow: hidden;
en el contenedor, pero con algunas diferencias.
Por un lado, el contenedor recibe el mismo tratamiento que bajo layout
contención: se convierte en un bloque contenedor con su propio contexto de apilamiento. Por lo tanto, haber colocado a los niños dentro de paint
la contención respetará el contenedor en términos de ubicación. Si duplicáramos la layout
demostración de contención anterior pero usáramos paint
contención en su lugar, el resultado sería más o menos el mismo. La diferencia es que las líneas moradas no desbordarían el contenedor cuando se aplica la contención, sino que se recortarían en el contenedor border-box
.
Otro beneficio interesante de paint
la contención es que el navegador puede omitir los descendientes de ese elemento en los cálculos de pintura si puede detectar que el contenedor en sí no es visible dentro de la ventana gráfica. Si el contenedor no está en la ventana gráfica o está oscurecido de alguna manera, entonces es una garantía de que sus descendientes tampoco serán visibles. Como ejemplo, piense en un menú de navegación que normalmente se encuentra fuera de la pantalla, a la izquierda de la página y se desliza hacia adentro cuando se hace clic en un botón. Cuando ese menú está en su estado normal fuera de la pantalla, el navegador simplemente deja de intentar pintar su contenido.
Contenciones trabajando juntas
Estas tres contenciones proporcionan diferentes formas de influir en partes de los cálculos de representación realizados por el navegador. size
La contención le dice al navegador que este contenedor no debe causar cambios de posición en la página cuando cambia su contenido. layout
La contención le dice al navegador que los descendientes de este contenedor no deben provocar cambios de diseño en elementos fuera de su contenedor y viceversa. paint
La contención le dice al navegador que el contenido de este contenedor nunca se pintará fuera de las dimensiones del contenedor y, si el contenedor está oculto, no se moleste en pintar el contenido en absoluto.
Dado que cada uno de ellos proporciona diferentes optimizaciones, tendría sentido combinar algunas de ellas. La especificación realmente lo permite. Por ejemplo, podríamos usar layout
y paint
juntos como valores de la propiedad contener como este:
.el { contain: layout paint;}
Dado que esto es algo tan obvio, la especificación en realidad proporciona dos valores abreviados:
Taquigrafía | Escritura común a mano |
---|---|
content |
layout paint |
strict |
layout paint size |
El content
valor será el más común de usar en un proyecto web con una serie de elementos dinámicos, como contenedores múltiples grandes cuyo contenido cambia con el tiempo o debido a la actividad del usuario.
El strict
valor sería útil para contenedores que tienen un tamaño definido que nunca cambiará, incluso si el contenido cambia. Una vez colocado, mantendrá el tamaño previsto. Un ejemplo simple de esto es un div que contiene contenido publicitario externo de terceros, en dimensiones definidas por la industria, que no tiene relación con nada más en la página en cuanto a DOM.
Beneficios de rendimiento
Esta parte del artículo es difícil de explicar. El problema es que no hay muchas imágenes sobre los beneficios de rendimiento. La mayoría de los beneficios son optimizaciones detrás de escena que ayudan al navegador a decidir qué hacer en un diseño o cambio de pintura.
Como un intento de mostrar los contain
beneficios de rendimiento de la propiedad, hice un ejemplo simple que cambia el elemento font-size
en un elemento con varios hijos. Este tipo de cambio normalmente provocaría un rediseño, lo que también conduciría a un repintado de la página. El ejemplo cubre los valores contenidos de none
, content
y strict
.
Los botones de radio cambian el valor del contenido property
que se aplica al cuadro morado en el centro. El botón “cambiar tamaño de fuente” alternará el font-size
contenido del cuadro morado cambiando de clase. Desafortunadamente, este cambio de clase también es un posible desencadenante de un rediseño. Si tiene curiosidad, aquí hay una lista de situaciones en JavaScript y luego una lista similar para CSS que desencadena dichos cálculos de diseño y pintura. Apuesto a que hay más de lo que crees.
Mi proceso totalmente poco científico fue seleccionar el tipo de contenido, iniciar una grabación de interpretación en las herramientas de desarrollo de Chome, hacer clic en el botón, esperar el font-size
cambio y luego detener la grabación después de aproximadamente un segundo. Hice esto tres veces para cada tipo de contención para poder comparar varias grabaciones. Los números para este tipo de comparación están en milisegundos cada uno, pero hay una diferencia suficiente para tener una idea de los beneficios. Las cifras podrían ser potencialmente muy diferentes en una situación más real.
Pero hay algunas cosas a tener en cuenta además de los números brutos.
Al mirar la grabación, encontraba el área relevante en la línea de tiempo y me concentraba allí para seleccionar la tarea que cubre el cambio. Luego miraría el registro de eventos de la tarea para ver los detalles. Los eventos registrados fueron: recalcular estilo, diseño, actualizar árbol de capas, pintura y capas compuestas. Sumar los tiempos de todos esos nos da el tiempo total de la tarea.
Una cosa a tener en cuenta para los dos tipos de contención es que el evento de pintura se registró dos veces. Volveré a eso en un momento.
Completando la tarea en cuestión
Estos son los tiempos totales para los tres tipos de contención, tres ejecuciones cada uno:
Contención | Ejecutar 1 | Ejecutar 2 | Ejecutar 3 | Promedio |
---|---|---|---|---|
none |
24 ms | 33,8 ms | 23,3 ms | 27,03 ms |
content |
13,2 ms | 9 ms | 9,2 ms | 10,47 ms |
strict |
5,6 ms | 18,9 ms | 8,5 ms | 11 ms |
La mayor parte del tiempo se dedicó al diseño. Hubo picos aquí y allá a lo largo de los números, pero recuerde que estos son resultados anecdóticos no científicos. De hecho, la segunda tanda de strict
contención tuvo un resultado mucho mayor que las otras dos; Simplemente lo dejé ahí porque esas cosas suceden en el mundo real. Quizás la música que estaba escuchando en ese momento cambió de canción durante esa carrera, quién sabe. Pero puedes ver que las otras dos carreras fueron mucho más rápidas.
Entonces, según estos números, puede comenzar a ver que la contain
propiedad ayuda al navegador a renderizarse de manera más eficiente. Ahora imagine que mi pequeño cambio se multiplica por los muchos cambios realizados en el DOM y el estilo de una página web dinámica típica.
Donde las cosas se ponen más interesantes es en los detalles del evento de pintura.
Diseñar una vez, pintar dos veces.
Quédate conmigo aquí. Prometo que tendrá sentido.
Voy a utilizar la demostración anterior como base para las siguientes descripciones. Si desea seguirlo, vaya a la versión completa de la demostración y abra DevTools. Tenga en cuenta que debe abrir los detalles del “cuadro” y no la línea de tiempo “principal” una vez que ejecute la herramienta de rendimiento para ver lo que estoy a punto de describir.
De hecho, estoy tomando capturas de pantalla de la versión de “página completa” ya que DevTools funciona mejor con esa versión. Dicho esto, la versión “completa” normal debería dar aproximadamente la misma idea.
The paint event only fired once in the event log for the task that had no containment at all. Typically, the event didn’t take too long, ranging from 0.2 ms to 3.6 ms. The deeper details is where it gets interesting. In those details, it notes that the area of paint was the entire page. In the event log, if you hover on the paint event, DevTools will even highlight the area of the page that was painted. The dimensions in this case will be whatever the size of your browser viewport happens to be. It will also note the layer root of the paint.
Note that the page area to the left in the image is highlighted, even outside of the purple box. Over to the right, are the dimensions of the paint to the screen. That’s roughly the size of the viewport in this instance. For a future comparison, note the #document
as the layer root.
Keep in mind that browsers have the concept of layers for certain elements to help with painting. Layers are usually for elements that may overlap each other due to a new stacking context. An example of this is the way applying position: relative;
and z-index: 1;
to an element will cause the browser to create that element as a new layer. The contain property has the same effect.
There is a section in DevTools called “rendering” and it provides various tools to see how the browser renders the page. When selecting the checkbox named “Layer borders” we can see different things based on the containment. When the containment is none then you should see no layers beyond the typical static web page layers. Select content
or strict
and you can see the purple box get converted to its own layer and the rest of the layers for the page shift accordingly.
It may be hard to notice in the image, but after selecting content
containment the purple box becomes its own layer and the page has a shift in layers behind the box. Also notice that in the top image the layer line goes across on top of the box, while in the second image the layer line is below the box.
I mentioned before that both content
and strict
causes the paint to fire twice. This is because two painting processes are done for two different reasons. In my demo the first event is for the purple box and the second is for the contents of the purple box.
Typically the first event will paint the purple box and report the dimensions of that box as part of the event. The box is now its own layer and enjoys the benefits that applies.
The second event is for the contents of the box since they are scrolling elements. As the spec explains; since the stacking context is guaranteed, scrolling elements can be painted into a single GPU layer. The dimensions reported in the second event is taller, the height of the scrolling elements. Possibly even narrower to make room for the scrollbar.
Note the difference in dimensions on the right of both of those images. Also, the layer root for both of those events is main.change
instead of the #document
seen above. The purple box is a main
element, so only that element was painted as opposed as to whole document. You can see the box being highlighted as opposed to the whole page.
The benefits of this is that normally when scrolling elements come into view, they have to be painted. Scrolling elements in containment have already been painted and don’t require it again when coming into view. So we get some scrolling optimizations as well.
Again, this can be seen in the demo.
Back to that Rendering tab. This time, check “Scrolling performance issue” instead. When the containment is set to none
, Chrome covers the purple box with an overlay that’s labeled “repaints on scroll.”
If you wish to see this happen live, check the “Paint flashing” option.
Please note: if flashing colors on the screen may present an issue for you in some way, please consider not checking the “Paint flashing” option. In the example I just described, not much changes on the page, but if one were to leave that checked and visited other sites, then reactions may be different.
With paint flashing enabled, you should see a paint indicator covering all the text within the purple box whenever you scroll inside it. Now change the containment to content
or strict
and then scroll again. After the first initial paint flash it should never reappear, but the scrollbar does show indications of painting while scrolling.
Also notice that the “repaints on scroll” overlay is gone on both forms of containment. In this case, containment has given us not only some performance boost in painting but in scrolling as well.
An interesting accidental discovery
As I was experimenting with the demo above and finding out how the paint and scrolling performance aspects worked, I came across an interesting issue. In one test, I had a simple box in the center of page, but with minimal styling. It was essentially an element that scrolls with lots of text content. I was applying content
containment to the container element, but I wasn’t seeing the scrolling performance benefits described above.
The container was flagged with the “repaints on scroll” overlay and the paint flashing was the same as no containment applied, even though I knew for a fact that content
containment was being applied to the container. So I started comparing my simple test against the more styled version I discussed above.
I eventually saw that if the background-color
of the container is transparent, then the containment scroll performance benefits do not happen.
I ran a similar performance test where I would change the font-size
of the contents to trigger the re-layout and repaint. Both tests had roughly the same results, with only difference that the first test had a transparent background-color
and the second test had a proper background-color. By the numbers, it looks like the behind-the-scenes calculations are still more performant; only the paint events are different. It appears the element doesn’t become its own layer in the paint calculations with a transparent background-color
.
The first test run only had one paint event in the event log. The second test run had the two paint events as I would expect. Without that background color, it seems the browser decides to skip the layer aspect of the containment. I even found that faking transparency by using the same color as the color behind the element works as well. My guess is if the container’s background is transparent then it must rely on whatever is underneath, making it impossible to separate the container to its own paint layer.
I made another version of the test demo that changes the background-colo
r of the container element from transparent to the same color used for the background color of the body. Here are two screenshots showing the differences when using the various options in the Rendering panel in DevTools.
You can see the checkboxes that have been selected and the result to the container. Even with a content containment applied, the box has “repaints on scroll” as well as the green overlay showing painting while scrolling.
In the second image, you can see that the same checkboxes are selected and a different result to the container. The “repaints on scroll” overlay is gone and the green overlay for painting is also gone. You can see the paint overlay on the scrollbar to show it was active.
Conclusion: make sure to apply some form of background color to your container when applying containment to get all the benefits.
Here’s what I used for the test:
This is the bottom of the page
This article has covered the basics of the CSS contain
property with its values, benefits, and potential performance gains. There are some excellent benefits to applying this property to certain elements in HTML; which elements need this applied is up to you. At least, that’s what I gather since I’m unaware of any specific guidance. The general idea is apply it to elements that are containers of other elements, especially those with some form of dynamic aspect to them.
Some possible scenarios: grid areas of a CSS grid, elements containing third-party content, and containers that have dynamic content based on user interaction. There shouldn’t be any harm in using the property in these cases, assuming you aren’t trying to contain an element that does, in fact, rely in some way on another element outside that containment.
Browser support is very strong. Safari is the only holdout at this point. You can still use the property regardless because the browser simply skips over that code without error if it doesn’t understand the property or its value.
So, feel free to start containing your stuff!
Deja un comentario