CSS no es magia negra

Retirar las cortinas de tus hojas de estilo

Si es un desarrollador web, es probable que tenga que escribir algo de CSS de vez en cuando.

Cuando miraste CSS por primera vez, probablemente parecía una brisa. Agregó algunos bordes aquí, cambió algunos colores allí. JavaScript es la parte difícil del desarrollo front-end, ¿verdad?

Sin embargo, en algún momento durante tu progresión como desarrollador web, ¡eso cambió! Lo peor es que muchos desarrolladores de la comunidad front-end han llegado a descartar CSS como un lenguaje de juguete.

Sin embargo, la verdad es que cuando nos topamos con una pared, muchos de nosotros realmente no entendemos lo que nuestro CSS está haciendo bajo el capó.

Durante los primeros dos años después de mi bootcamp, hice JavaScript de pila completa y rocié un poco de CSS aquí y allá. Como panelista en JavaScript Jabber, siempre sentí que JavaScript era mi pan de cada día, así que es en lo que pasé más tiempo.

El año pasado, sin embargo, decidí centrarme en el front end y me di cuenta de que no podía depurar mis hojas de estilo de la misma manera que hice mi JavaScript.

A todos nos gusta hacer bromas al respecto, pero cuántos de nosotros nos hemos tomado el tiempo para tratar de entender el CSS que estamos escribiendo o leyendo. ¿Cuántos de nosotros hemos depurado razonablemente un problema en la siguiente capa de abstracción más baja cuando llegamos a un muro? En cambio, nos conformamos con la primera respuesta de StackOverflow, los hacks, o simplemente dejamos que el problema desaparezca por completo.

Con demasiada frecuencia, los desarrolladores quedan completamente desconcertados cuando el navegador procesa CSS de una manera que no esperaban. Sin embargo, no es magia oscura y, como desarrolladores, sabemos que las computadoras solo están analizando nuestras instrucciones.

El conocimiento de los componentes internos también puede ser útil para la depuración avanzada y el ajuste del rendimiento. Si bien muchas charlas de la conferencia discuten cómo solucionar errores comunes, mi charla (y esta publicación) se centrará en el por qué al profundizar en los aspectos internos del navegador para ver cómo se analizan y se representan nuestros estilos.

El DOM y CSSOM

Primero, es importante comprender que los navegadores contienen un motor de JavaScript y un motor de representación. Nos centraremos en lo último. Por ejemplo, discutiremos detalles relacionados con WebKit (Safari), Blink (Chrome), Gecko (Firefox) y Trident / EdgeHTML (IE / Edge). El navegador se someterá a un proceso que incluye conversión, tokenización, lexing y análisis que finalmente construye DOM y CSSOM.

A un alto nivel, puede pensar en ellos como lo siguiente:

  • Conversión: Lectura de bytes sin formato de HTML y CSS del disco o la red.
  • Tokenización: romper la entrada en fragmentos (por ejemplo, etiquetas de inicio, etiquetas finales, nombres de atributos, valores de atributos), eliminando caracteres irrelevantes como espacios en blanco y saltos de línea.
  • Lexing: al igual que el tokenizer, pero también identifica el tipo de cada token (este token es un número, ese token es un literal de cadena, este otro token es un operador de igualdad).
  • Análisis: toma el flujo de tokens del lexer, interpreta los tokens usando una gramática específica y lo convierte en un árbol de sintaxis abstracta.

Una vez que se crean ambas estructuras de árbol, el motor de representación adjunta las estructuras de datos en lo que se llama un árbol de representación como parte del proceso de diseño.

El árbol de representación es una representación visual del documento que permite pintar el contenido de la página en su orden correcto. La construcción del árbol de renderización sigue el siguiente orden:

  • Comenzando en la raíz del árbol DOM, atraviese cada nodo visible.
  • Omitir nodos no visibles.
  • Para cada nodo visible, encuentre las reglas CSSOM coincidentes apropiadas y aplíquelas.
  • Emite nodos visibles con contenido y sus estilos calculados.
  • Finalmente, genere un árbol de representación que contenga tanto el contenido como la información de estilo de todo el contenido visible en la pantalla.

El CSSOM puede tener efectos drásticos en el árbol de renderizado pero ninguno en el árbol DOM.

Representación

Después del diseño y la construcción del árbol de renderizado, el navegador finalmente puede proceder a la pintura real de la pantalla y la composición. Tomemos un breve momento para distinguir entre alguna terminología aquí.

  • Diseño: incluye el cálculo de cuánto espacio ocupará un elemento y dónde está en la pantalla. Los elementos principales pueden afectar a los elementos secundarios y, a veces, viceversa.
  • Pintura: El proceso de convertir cada nodo en el árbol de renderizado a píxeles reales en la pantalla. Implica dibujar texto, colores, imágenes, bordes y sombras. El dibujo generalmente se realiza en múltiples capas y múltiples rondas de pintura pueden ser causadas por la carga de JavaScript que cambia el DOM.
  • Composición: la acción de aplanar todas las capas en la imagen final que es visible en la pantalla. Dado que partes de la página se pueden dibujar en varias capas, deben dibujarse en la pantalla en el orden correcto.

El tiempo de pintura varía según la construcción del árbol de renderizado y cuanto mayor sea el ancho y la altura del elemento, mayor será el tiempo de pintura.

Agregar diferentes efectos también puede aumentar el tiempo de pintura. La pintura sigue el orden en que los elementos se apilan en sus contextos de apilamiento (de atrás hacia adelante) en los que nos encontraremos cuando más adelante hablemos del índice z. Si eres un aprendiz visual, hay una gran demostración de pintura.

Cuando las personas hablan de la aceleración de hardware en los navegadores, casi siempre se refieren a la composición acelerada, lo que solo significa usar la GPU para componer los contenidos de una página web.

La composición permite aumentos de velocidad bastante grandes en comparación con la forma anterior que usaba la CPU de la computadora. La propiedad will-change es una propiedad que puede agregar que aprovechará esto.

Por ejemplo, cuando se utilizan transformaciones CSS, la propiedad will-change permite insinuar al navegador que un elemento DOM se transformará en un futuro próximo. Esto permite descargar algunas operaciones de dibujo y composición en la GPU, lo que puede mejorar en gran medida el rendimiento de las páginas con muchas animaciones. Tiene ganancias similares para la posición de desplazamiento, contenido, opacidad y posicionamiento superior o izquierdo.

Es importante comprender que ciertas propiedades causarán una retransmisión, mientras que otras propiedades solo causarán un repintado. Por supuesto, en cuanto al rendimiento, es mejor si solo puede desencadenar un repintado.

Por ejemplo, los cambios en el color de un elemento solo volverán a pintar ese elemento, mientras que los cambios en la posición del elemento provocarán el diseño y el repintado de ese elemento, sus elementos secundarios y posiblemente hermanos. Agregar un nodo DOM causará el diseño y el repintado del nodo. Los cambios importantes, como aumentar el tamaño de la fuente de un elemento html, provocarán una retransmisión y repintado de todo el árbol.

Si eres como yo, probablemente estés más familiarizado con el DOM que con el CSSOM, así que profundicemos un poco en eso. Es importante tener en cuenta que, de forma predeterminada, CSS se trata como un recurso de bloqueo de representación. Esto significa que el navegador mantendrá la representación de cualquier otro proceso hasta que se construya el CSSOM.

El CSSOM tampoco es 1 a 1 con el DOM. No mostrar ninguno, las etiquetas de script, metaetiquetas, elemento de cabecera, etc. se omiten ya que no se reflejan en la salida representada.

Otra diferencia entre el CSSOM y el DOM es que el análisis CSS utiliza una gramática libre de contexto. En otras palabras, el motor de renderizado no tiene código que llene la sintaxis faltante para CSS como lo hará al analizar HTML para crear el DOM.

Al analizar HTML, el navegador debe tener en cuenta los caracteres que lo rodean y necesita más que solo la especificación, ya que el marcado podría contener información faltante, pero aún deberá procesarse sin importar qué.

Con todo lo dicho, recapitulemos.

  • El navegador envía una solicitud HTTP para la página
  • El servidor web envía una respuesta
  • El navegador convierte los datos de respuesta (bytes) en tokens, mediante tokenización
  • El navegador convierte los tokens en nodos
  • El navegador convierte los nodos en el árbol DOM
  • Espera la construcción del árbol CSSOM

Especificidad

Ahora que tenemos una mejor comprensión de cómo funciona el navegador bajo el capó, echemos un vistazo a algunas de las áreas más comunes de confusión para los desarrolladores. Primero, especificidad.

En un nivel muy básico, sabemos que la especificidad solo significa aplicar reglas en el orden correcto en cascada. Sin embargo, hay muchas formas de apuntar a una etiqueta específica utilizando selectores CSS, y el navegador necesita una forma de negociar qué estilos darle a una etiqueta específica. Los navegadores toman esta decisión calculando primero el valor de especificidad de cada selector.

Lamentablemente, el cálculo de especificidad ha desconcertado a muchos desarrolladores de JavaScript, así que profundicemos en cómo se realiza este cálculo. Utilizaremos un ejemplo de un div con una clase de "contenedor". Anidado dentro de ese div tendremos otro div con una identificación de "main". Dentro de eso tendremos una etiqueta p que contiene una etiqueta de anclaje. Sin mirar hacia adelante, ¿sabes de qué color será la etiqueta de anclaje?

#main a {
  color verde;
}
p a {
  color amarillo;
}
.container #main a {
  color rosa;
}
div #main p a {
  Color naranja;
}
una {
  color rojo;
}

La respuesta es rosa, con un valor de 1,1,1. Aquí están los resultados restantes:

  • div #principal p a: 1,0,3
  • #principal a: 1,0,1
  • p a: 2
  • a: 1

Para determinar el número, debe calcular lo siguiente:

  • Primer número: el número de selectores de ID.
  • Segundo número: El número de selectores de clase, selectores de atributos (ej: [type = "text"], [rel = "nofollow"]) y pseudo-clases (ej:: hover,: visitado).
  • Tercer número: el número de selectores de tipo y pseudoelementos (ej .: :: before, :: after).

Entonces, para un selector que se ve así:

#header .navbar li a: visitado

El valor será 1,2,2 porque tenemos una ID, una clase, una pseudoclase y dos selectores de tipo (li, a). Puede leer los valores como si fueran solo un número, como 1,2,2 es 122. Las comas están ahí para recordarle que este no es un sistema de base 10. Técnicamente, podría tener un valor de especificidad de 0,1,13,4 y 13 no se desbordaría como lo haría un sistema de base 10.

Posicionamiento

Segundo, quiero tomarme un momento para discutir el posicionamiento. El posicionamiento y el diseño van de la mano como vimos anteriormente en esta publicación.

El diseño es un proceso recursivo que se puede activar en todo el árbol de renderizado como resultado de un cambio de estilo global o de forma incremental donde solo se colocarán partes sucias de la página. Una cosa interesante a tener en cuenta si pensamos en el árbol de renderizado es que con una posición absoluta, el objeto que se presenta se coloca en el árbol de renderizado en un lugar diferente que en el árbol DOM.

También me preguntan con frecuencia sobre el uso de flexbox versus flotantes. Por supuesto, flexbox es excelente desde el punto de vista de la usabilidad, pero cuando se aplica al mismo elemento, un diseño de flexbox se procesará en aproximadamente 3.5 ms, mientras que un diseño flotante puede tomar alrededor de 14 ms. Por lo tanto, vale la pena mantenerse al día con sus habilidades de CSS tanto como lo hace con sus habilidades de JavaScript.

Índice Z

Finalmente, quiero discutir el índice z. Al principio, suena simple. Cada elemento en un documento HTML puede estar delante o detrás de cualquier otro elemento en el documento. También solo funciona en elementos posicionados. Si intenta establecer un índice z en un elemento sin posición especificada, no hará nada.

La clave para depurar problemas del índice z es comprender los contextos de apilamiento y comenzar siempre en el elemento raíz de los contextos de apilamiento. Un contexto de apilamiento es solo una conceptualización tridimensional de elementos HTML a lo largo de un eje z imaginario en relación con el usuario frente a la ventana gráfica. En otras palabras, son grupos de elementos con un padre común que se mueven hacia adelante o hacia atrás juntos.

Cada contexto de apilamiento tiene un único elemento HTML como elemento raíz y cuando las propiedades de índice z y posición no están involucradas, las reglas son simples. El orden de apilamiento es el mismo que el orden de aparición en el HTML.

Sin embargo, puede crear nuevos contextos de apilamiento con propiedades que no sean el índice z y aquí es donde las cosas se complican. La opacidad, cuando su valor es menor que uno, se filtra cuando su valor es diferente a ninguno, y el modo de mezcla y mezcla cuando su valor es diferente a lo normal creará nuevos contextos de apilamiento.

Solo un recordatorio, el modo de fusión determina cómo los píxeles en una capa específica interactúan con los píxeles visibles en las capas debajo de ella.

La propiedad de transformación también activa un nuevo contexto de apilamiento cuando su valor no es ninguno. Por ejemplo, scale (1) y translate3d (0,0,0). Nuevamente, como recordatorio, la propiedad de escala se usa para ajustar el tamaño, y translate3d activa la GPU en acción para las transiciones CSS, haciéndolas más suaves.

Por lo tanto, es posible que aún no tenga ojo para el diseño, ¡pero con suerte ahora se está alejando de un gurú de CSS! Si está interesado en ir aún más lejos, he compilado recursos adicionales que también utilicé aquí.