Las hojas de estilo en cascada o CSS son indispensables en cualquier aplicación Web no trivial. Nos permiten definir el estilo visual (e incluso el comportamiento) que mostrarán los diferentes elementos de contenido de una página o aplicación Web. Sin ellas serían imposibles la separación entre contenido y visualización o la adaptación de un mismo diseño de página a diferentes dispositivos.
Todos estamos acostumbrados a escribir CSS con sus diversas combinaciones de selectores, y cuando adquieren su verdadera potencia es, precisamente, al combinar las definiciones de selectores para poder asignar estilos con mucha exactitud.
Por ejemplo, imaginemos que tenemos una página con multitud de listas no ordenadas (por ejemplo colecciones de recursos en Internet) y queremos darle un estilo particular a los elementos de determinadas de estas listas. Podríamos especificar un selector como este:
1: .recursos li {
2: font-weight: bold;
3: font-size: large;
4: }
Con ello estamos indicando al navegador que cuando se encuentre con un elemento de lista que esté contenido dentro de un elemento que tenga aplicada la clase “recursos”, ponga su letra en tamaño grande y con negrita.
Así que si tenemos una lista como esta en la página:
1: <ul class="recursos">
2: <li>Krasis</li>
3: <li>MAILCast</li>
4: <li>campusMVP</li>
5: <li>SELF</li>
6: </ul>
Lo que obtendríamos sería algo similar a esto:
Hasta aquí todo muy simple y normal. No parece haber nada raro ¿verdad?
Bueno, en realidad no hay nada raro pero lo cierto es que en páginas grandes en las que debiésemos aplicar un estilo así podríamos tener problemas de rendimiento. Ahora explico por qué.
El “extraño” comportamiento de los navegadores
Cuando un programador ve un selector de estilos como el anterior lo primero que piensa, como es normal, es que el navegador buscará todos los elementos que tengan aplicada la clase “recursos” y luego dentro de cada uno de esos elementos buscará los elementos de lista, de modo que le aplicará el estilo indicado a los que encuentre ¿verdad?
Sin embargo los navegadores no trabajan de esa manera, sino de otra que puede parecer anti-intuitiva para la mayor parte de las situaciones.
La forma de proceder es justo la contraria. El navegador no carga la CSS y la procesa para aplicarla, sino que simplemente la procesa para crear una “base de datos en memoria” de las reglas que tiene definidas. Es más tarde al ir procesando el árbol de elementos de la página donde para cada elemento examina qué reglas le pueden corresponder y se las aplica.
Este comportamiento es lógico ya que las hojas CSS pueden contener cientos o miles de selectores, y una página sin embargo contiene habitualmente muchos menos elementos. Cuánto más tarde el navegador en procesar la página completa (incluidos los estilos) peor será la experiencia de usuario y menos útil y peor valorado estará. Así que es crucial que todo se procese a la mayor velocidad posible, y es más eficiente hacerlo a partir de los elementos que a partir de las definiciones contenidas en la CSS.
Así que lo normal es asumir que la mayor parte de los selectores CSS no van a coincidir con la mayoría de los elementos, lo cual es cierto para la mayoría de las páginas. Ciertas aplicaciones Web grandes pueden tener perfectamente más de mil selectores en sus hojas CSS (si no me crees echa un vistazo a SkyDrive o a GMail por ejemplo), por lo que buscar en la página los elementos que coincidan con el selector de cada una de ellas puede tardar muchísimo, y se hace justo al revés que es mucho más rápido y eficiente.
Esto implica que para un selector CSS como el del ejemplo:
lo que se hace es primero buscar todos los li y luego ver cuáles están contenidos dentro de algún elemento con la clase “recursos”.
Es decir, en HTML los selectores CSS se procesan de derecha a izquierda y no al revés que parece más intuitivo.
En realidad, para ser más exactos, cuando el procesador de la página se encuentra con un elemento <li> lo que hace es ver qué reglas CSS existen que afecten a los elementos de este tipo. En nuestro ejemplo el selector le fuerza comprobar si cada <li> está o no contenido en algún nivel de la jerarquía dentro de un elemento con la clase “recursos” y si es así le aplica lo que indica el estilo para ese selector.
Si nos imaginamos una página con muy pocos estilos parece poco razonable actuar así. Sin embargo cuando la página contiene relativamente pocos elementos y muchos selectores CSS este modo de proceder hace que el procesamiento gane mucho en velocidad y eficiencia.
¿Y esto en qué me afecta?
Todo lo anterior parece mucho más ineficiente que procesar el selector y por lo tanto buscar todos los <li> contenidos en elementos con la clase “recursos”. Y de hecho lo es para este caso considerado aisladamente. Pero como ha quedado demostrado en páginas con muchos selectores es mucho más rápido en realidad que hacerlo de la forma que nos parece más intuitiva a priori.
En realidad en una página común todo esto no tiene demasiada importancia más allá de ser una curiosidad teórica, ya que en cualquier caso el procesamiento será muy rápido. Sin embargo en aplicaciones o sitios webs complejos como los mencionados, cada décima de segundo cuenta para dar una respuesta rápida a los usuarios y hacerlos más agradables de usar. Si cada vez que se cargase una carpeta de correo en Hotmail se tardase medio segundo más en visualizar la página la gente huiría en masa del servicio.
Ahora considera de nuevo nuestro sencillo ejemplo de las listas de recursos con el selector anterior, pero que actúa dentro de una página muy grande, que contenga miles de elementos <li> en nuestras listas de recursos. Podría ser por ejemplo una página auto-generada con listas enormes de datos generadas desde una base de datos, algunos de los cuales son recursos y se les debe aplicar el selector de ejemplo anterior, y otros son elementos de otros tipos de listas que no llevan ese estilo.
Con el nuevo conocimiento que hemos adquirido sobre cómo funciona el procesamiento de selectores lo que ocurriría es que por cada elemento <li> que se encuentra al procesar el DOM, el navegador tiene que mirar qué estilo le debe aplicar, y por lo tanto para cada uno de ellos debe comprobar en nuestro ejemplo si tiene un padre en algún nivel superior de la página que tenga aplicada la clase “recursos”. Esto hará que la aplicación de los estilos sea muy ineficiente y costosa. Y más si consideramos que puede haber listas anidadas y otros selectores CSS que le afecten.
Sabiendo esto podemos solucionar el posible problema cambiando el modo de seleccionar los elementos. Por ejemplo, sería mucho mejor aplicarles un identificador o una clase específica para que el navegador localice el estilo a aplicarles de manera directa.
Como digo, en cualquier página normal (incluso aunque tenga mil elementos <li>) el impacto de una ineficiencia así será inapreciable. Sin embargo en páginas complejas con muchos estilos solapados y muchos elementos a los que aplicárselos (piensa en GMail o SkyDirve) el impacto puede ser muy apreciable. Así que si el rendimiento nos importa debemos analizar más a fondo nuestros selectores CSS.
Obviamente esta es una sola de las muchas optimizaciones que podemos aplicar al CSS, pero como es muy poco conocida me ha parecido interesante resaltarla aquí.
¡Espero que te sea útil!