
Introducción y guía al modelo de objetos CSS (CSSOM)

- ¿Qué es el CSSOM?
- Estilos en línea a través de element.style
- La API de declaración CSSStyle
- setProperty(), getPropertyValue() y elemento()
- Usando removeProperty()
- Obtener y establecer la prioridad de una propiedad
- Acceder a las reglas @media con CSSOM
- Acceder a las reglas @keyframes con CSSOM
- Cómo agregar y eliminar declaraciones CSS
- Revisando la API CSSStyleDeclaration
Si ha estado escribiendo JavaScript durante algún tiempo, es casi seguro que haya escrito algunos scripts relacionados con el modelo de objetos de documento (DOM). Las secuencias de comandos DOM aprovechan el hecho de que una página web abre un conjunto de API (o interfaces) para que pueda manipular y tratar con elementos de una página.
Pero hay otro modelo de objetos con el que quizás quieras familiarizarte más: el modelo de objetos CSS (CSSOM). Probablemente ya lo hayas usado pero no necesariamente te hayas dado cuenta.
En esta guía, repasaré muchas de las características más importantes del CSSOM, comenzando con las cosas más conocidas y luego pasando a algunas características más oscuras pero prácticas.
¿Qué es el CSSOM?
Según MDN:
El modelo de objetos CSS es un conjunto de API que permiten la manipulación de CSS desde JavaScript. Es muy parecido al DOM, pero para CSS en lugar de HTML. Permite a los usuarios leer y modificar el estilo CSS de forma dinámica.
La información de MDN se basa en la especificación CSSOM oficial del W3C. Ese documento del W3C es una forma algo decente de familiarizarse con lo que es posible con CSSOM, pero es un completo desastre para cualquiera que busque algunos ejemplos prácticos de codificación que pongan las API de CSSOM en acción.
MDN es mucho mejor, pero aún falta en ciertas áreas. Entonces, para esta publicación, intenté hacer lo mejor que pude para crear ejemplos de código útiles y demostraciones de estas interfaces en uso, para que puedas ver las posibilidades y jugar con el código en vivo.
Como se mencionó, la publicación comienza con cosas que ya son familiares para la mayoría de los desarrolladores de aplicaciones para el usuario. Estas características comunes generalmente se agrupan con las secuencias de comandos DOM, pero técnicamente son parte del grupo más grande de interfaces disponibles a través de CSSOM (aunque también se cruzan con el DOM).
Estilos en línea a través de element.style
La forma más básica de manipular o acceder a propiedades y valores CSS utilizando JavaScript es a través del style
objeto o propiedad, que está disponible en todos los elementos HTML. He aquí un ejemplo:
{ content: 'Example'; display: block; width: 50px;}
Aquí estoy agregando un ::before
pseudoelemento dentro del .box
elemento. Con el siguiente código JavaScript, puedo acceder a los estilos calculados para ese pseudoelemento:
').width;// "50px"
También puedes hacer esto para otros pseudoelementos como ::first-line
, como en el siguiente código y demostración:
let p = document.querySelector('.box p');window.getComputedStyle(p, '::first-line').color;
Y aquí hay otro ejemplo que utiliza el ::placeholder
pseudo-elemento, que se aplica a input
los elementos:
let input = document.querySelector('input');window.getComputedStyle(input, '::placeholder').color
Lo anterior funciona en la última versión de Firefox, pero no en Chrome o Edge (presenté un informe de error para Chrome).
También debe tenerse en cuenta que los navegadores obtienen resultados diferentes cuando intentan acceder a los estilos de un pseudoelemento inexistente (pero válido) en comparación con un pseudoelemento que el navegador no admite en absoluto (como un ::banana
pseudoelemento inventado). Puede probar esto en varios navegadores utilizando la siguiente demostración:
Como punto adicional a esta sección, hay un método exclusivo de Firefox llamado getDefaultComputedStyle()
que no forma parte de la especificación y probablemente nunca lo será.
La API de declaración CSSStyle
Anteriormente, cuando les mostré cómo acceder a las propiedades a través del style
objeto o usando getComputedStyle()
, en ambos casos esas técnicas exponían la CSSStyleDeclaration
interfaz.
En otras palabras, las dos líneas siguientes devolverán un CSSStyleDeclaration
objeto en el elemento del documento body
:
document.body.style;window.getComputedStyle(document.body);
En la siguiente captura de pantalla puedes ver lo que produce la consola para cada una de estas líneas:
En el caso de getComputedStyle()
, los valores son de solo lectura. En el caso de element.style
, es posible obtener y configurar los valores pero, como se mencionó anteriormente, estos solo afectarán los estilos en línea del documento .
setProperty(), getPropertyValue() y elemento()
Una vez que hayas expuesto un CSSStyleDeclaration
objeto de una de las formas anteriores, tendrás acceso a una serie de métodos útiles para leer o manipular los valores. Nuevamente, los valores son de solo lectura en el caso de getComputedStyle()
, pero cuando se usan a través de la style
propiedad, hay algunos métodos disponibles tanto para obtenerlos como para configurarlos.
Considere el siguiente código y demostración:
let box = document.querySelector('.box');box.style.setProperty('color', 'orange');box.style.setProperty('font-family', 'Georgia, serif');op.innerHTML = box.style.getPropertyValue('color');op2.innerHTML = `${box.style.item(0)}, ${box.style.item(1)}`;
En este ejemplo, estoy usando tres métodos diferentes del style
objeto:
- El
setProperty()
método. Este utiliza dos argumentos, cada uno de ellos una cadena: la propiedad (en notación CSS normal) y el valor que desea asignar a la propiedad. - El
getPropertyValue()
método. Esto toma un solo argumento: La propiedad cuyo valor desea obtener. Este método se utilizó en un ejemplo anterior usandogetComputedStyle()
, que, como se mencionó, también expone unCSSStyleDeclaration
objeto. - El
item()
método. Esto requiere un único argumento, que es un número entero positivo que representa el índice de la propiedad a la que desea acceder. El valor de retorno es el nombre de la propiedad en ese índice.
Tenga en cuenta que en mi ejemplo simple anterior, solo hay dos estilos agregados al CSS en línea del elemento. Esto significa que si tuviera que acceder a item(2)
, el valor de retorno sería una cadena vacía. Obtendría el mismo resultado si accediera getPropertyValue()
a una propiedad que no está configurada en los estilos en línea de ese elemento.
Usando removeProperty()
Además de los tres métodos mencionados anteriormente, hay otros dos expuestos en un CSSStyleDeclaration
objeto. En el código y la demostración siguientes, utilizo el removeProperty()
método:
box.style.setProperty('font-size', '1.5em');box.style.item(0) // "font-size"document.body.style.removeProperty('font-size');document.body.style.item(0); // ""
En este caso, después de configurar font-size
el uso setProperty()
, registro el nombre de la propiedad para asegurarme de que esté allí. Luego, la demostración incluye un botón que, al hacer clic, eliminará la propiedad usando removeProperty()
.
En el caso de setProperty()
y removeProperty()
, el nombre de la propiedad que ingresa tiene un guión (el mismo formato que en su hoja de estilo), en lugar de estar escrito en camello. Esto puede parecer confuso al principio, pero el valor pasado es una cadena en este ejemplo, por lo que tiene sentido.
Obtener y establecer la prioridad de una propiedad
Finalmente, aquí hay una característica interesante que descubrí mientras investigaba este artículo: el getPropertyPriority()
método, demostrado con el código y CodePen a continuación:
box.style.setProperty('font-family', 'Georgia, serif', 'important');box.style.setProperty('font-size', '1.5em');box.style.getPropertyPriority('font-family'); // importantop2.innerHTML = box.style.getPropertyPriority('font-size'); // ""
En la primera línea de ese código, puedes ver que estoy usando el setProperty()
método, como lo hice antes. Sin embargo, observa que he incluido un tercer argumento. El tercer argumento es una cadena opcional que define si quieres que la propiedad tenga la !important
palabra clave adjunta.
Después de configurar la propiedad con !important
, uso el getPropertyPriority()
método para verificar la prioridad de esa propiedad. Si desea que la propiedad no tenga importancia, puede omitir el tercer argumento, usar la palabra clave undefined
o incluir el tercer argumento como una cadena vacía.
Y debo enfatizar aquí que estos métodos funcionarían en conjunto con cualquier estilo en línea ya colocado directamente en el HTML en el style
atributo de un elemento.
eyframes exampleAnimation { from { color: blue; } 20% { color: orange; } to { color: green; }}code { color: firebrick;}
Hay varias cosas diferentes que puedo intentar con esta hoja de estilo de ejemplo y demostraré algunas de ellas aquí. Primero, voy a recorrer todas las reglas de estilo en la hoja de estilo y registrar el texto del selector para cada una:
let myRules = document.styleSheets[0].cssRules, p = document.querySelector('p');for (i of myRules) { if (i.type === 1) { p.innerHTML += `code${i.selectorText}/codebr`; }}
Un par de cosas que se deben tener en cuenta en el código y la demostración anteriores. Primero, almaceno en caché una referencia al cssRules
objeto para mi hoja de estilo. Luego, hago un bucle sobre todas las reglas de ese objeto y verifico de qué tipo es cada una.
En este caso, quiero reglas que sean del tipo 1
, que representa la STYLE_RULE
constante. Otras constantes incluyen IMPORT_RULE
(3), MEDIA_RULE
(4), KEYFRAMES_RULE
(7), etc. Puede ver una tabla completa de estas constantes en este artículo de MDN.
Cuando confirmo que una regla es una regla de estilo, imprimo la ectorTextpropiedad de cada una de esas reglas de estilo. Esto generará las siguientes líneas para la hoja de estilo especificada:
*bodymain.componenta:hovercode
La ectorTextpropiedad es una representación de cadena del selector utilizado en esa regla. Esta es una propiedad que se puede escribir, por lo que si quiero puedo cambiar el selector de una regla específica dentro de mi for
bucle original con el siguiente código:
if (i.selectorText === 'a:hover') { i.selectorText = 'a:hover, a:active';}
En este ejemplo, busco un selector que defina :hover
estilos en mis enlaces y expando el selector para aplicar los mismos estilos a los elementos del :active
estado. Alternativamente, podría usar algún tipo de método de cadena o incluso una expresión regular para buscar todas las instancias de :hover
y luego hacer algo a partir de ahí. Pero esto debería ser suficiente para demostrar cómo funciona.
Acceder a las reglas @media con CSSOM
Notarás que mi hoja de estilo también incluye una regla de consulta de medios y un bloque de reglas de fotogramas clave. Ambos se omitieron cuando busqué reglas de estilo (tipo 1). Ahora busquemos todas @media
las reglas:
let myRules = document.styleSheets[0].cssRules, p = document.querySelector('.output');for (i of myRules) { if (i.type === 4) { for (j of i.cssRules) { p.innerHTML += `code${j.selectorText}/codebr`; } }}
Según la hoja de estilo proporcionada, lo anterior producirá:
body.component
Como puede ver, después de recorrer todas las reglas para ver si @media
existe alguna (tipo 4), recorro el cssRules
objeto para cada regla de medios (en este caso, solo hay una) y registro el texto del selector para cada regla. dentro de esa regla mediática.
Por lo tanto, la interfaz que se expone en una @media
regla es similar a la interfaz que se expone en una hoja de estilo. @media
Sin embargo, la regla también incluye una conditionText
propiedad, como se muestra en el siguiente fragmento y demostración:
let myRules = document.styleSheets[0].cssRules, p = document.querySelector('.output');for (i of myRules) { if (i.type === 4) { p.innerHTML += `code${i.conditionText}/codebr`; // (max-width: 800px) }}
Este código recorre todas las reglas de consulta de medios y registra el texto que determina cuándo se aplica esa regla (es decir, la condición). También hay una mediaText
propiedad que devuelve el mismo valor. Según la especificación, puedes obtener o configurar cualquiera de estos.
Acceder a las reglas @keyframes con CSSOM
Ahora que he demostrado cómo leer información de una @media
regla, consideremos cómo acceder a una @keyframes
regla. Aquí hay un código para comenzar:
let myRules = document.styleSheets[0].cssRules, p = document.querySelector('.output');for (i of myRules) { if (i.type === 7) { for (j of i.cssRules) { p.innerHTML += `code${j.keyText}/codebr`; } }}
En este ejemplo, busco reglas que tengan un tipo 7 (es decir, @keyframes
reglas). Cuando encuentro una, reviso todas esas reglas cssRules
y registro la keyText
propiedad para cada una. El registro en este caso será:
"0%""20%""100%"
Notarás que mi CSS original usa from
y to
como el primer y último fotograma clave, pero la keyText
propiedad los calcula como 0%
y 100%
. El valor de keyText
también se puede configurar. En mi hoja de estilo de ejemplo, podría codificarlo de esta manera:
// Read the current value (0%)document.styleSheets[0].cssRules[6].cssRules[0].keyText;// Change the value to 10%document.styleSheets[0].cssRules[6].cssRules[0].keyText = '10%'// Read the new value (10%)document.styleSheets[0].cssRules[6].cssRules[0].keyText;
Usando esto, podemos alterar dinámicamente los fotogramas clave de una animación en el flujo de una aplicación web o posiblemente en respuesta a una acción del usuario.
Otra propiedad disponible al acceder a una @keyframes
regla es name
:
let myRules = document.styleSheets[0].cssRules, p = document.querySelector('.output');for (i of myRules) { if (i.type === 7) { p.innerHTML += `code${i.name}/codebr`; }}
Recuerde que en CSS la @keyframes
regla se ve así:
@keyframes exampleAnimation { from { color: blue; } 20% { color: orange; } to { color: green; }}
De esta forma, la name
propiedad me permite leer el nombre personalizado elegido para esa @keyframes
regla. Este es el mismo nombre que se usaría en la animation-name
propiedad al habilitar la animación en un elemento específico.
Una última cosa que mencionaré aquí es la capacidad de capturar estilos específicos que se encuentran dentro de un único fotograma clave. Aquí hay un código de ejemplo con una demostración:
let myRules = document.styleSheets[0].cssRules, p = document.querySelector('.output');for (i of myRules) { if (i.type === 7) { for (j of i.cssRules) { p.innerHTML += `code${j.style.color}/codebr`; } }}
En este ejemplo, después de encontrar la @keyframes
regla, recorro cada una de las reglas en el fotograma clave (por ejemplo, la regla “desde”, la regla “20%”, etc.). Luego, dentro de cada una de esas reglas, accedo a una style
propiedad individual. En este caso, dado que sé que color
es la única propiedad definida para cada una, simplemente estoy eliminando los valores de color.
La principal conclusión en este caso es el uso de la style
propiedad u objeto. Anteriormente mostré cómo se puede utilizar esta propiedad para acceder a estilos en línea. Pero en este caso, lo estoy usando para acceder a las propiedades individuales dentro de un único fotograma clave.
Probablemente puedas ver cómo esto abre algunas posibilidades. Esto te permite modificar las propiedades de un fotograma clave individual sobre la marcha, lo que podría suceder como resultado de alguna acción del usuario o de algo más que ocurra en una aplicación o, posiblemente, en un juego basado en la web.
Cómo agregar y eliminar declaraciones CSS
La CSSStyleSheet
interfaz tiene acceso a dos métodos que le permiten agregar o eliminar una regla completa de una hoja de estilo. Los métodos son: insertRule()
y deleteRule()
. Veámoslos a ambos en acción manipulando nuestra hoja de estilo de ejemplo:
let myStylesheet = document.styleSheets[0];console.log(myStylesheet.cssRules.length); // 8document.styleSheets[0].insertRule('article { line-height: 1.5; font-size: 1.5em; }', myStylesheet.cssRules.length);console.log(document.styleSheets[0].cssRules.length); // 9
En este caso, estoy registrando la longitud de la cssRules
propiedad (mostrando que la hoja de estilo originalmente tiene 8 reglas), luego agrego el siguiente CSS como una regla individual usando el insertRule()
método:
article { line-height: 1.5; font-size: 1.5em;}
Vuelvo a registrar la longitud de la cssRules
propiedad para confirmar que se agregó la regla.
El insertRule()
método toma una cadena como primer parámetro (que es obligatorio), que comprende la regla de estilo completa que desea insertar (incluido el selector, las llaves, etc.). Si está insertando una regla at, entonces se puede incluir en esta cadena la regla at completa, incluidas las reglas individuales anidadas dentro de la regla at.
El segundo argumento es opcional. Este es un número entero que representa la posición, o índice, donde desea insertar la regla. Si no se incluye, el valor predeterminado es 0
(lo que significa que la regla se insertará al principio de la colección de reglas). Si el índice es mayor que la longitud del objeto de reglas, generará un error.
El deleteRule()
método es mucho más sencillo de utilizar:
let myStylesheet = document.styleSheets[0];console.log(myStylesheet.cssRules.length); // 8myStylesheet.deleteRule(3);console.log(myStylesheet.cssRules.length); // 7
En este caso, el método acepta un único argumento que representa el índice de la regla que quiero eliminar.
Con cualquiera de los métodos, debido a la indexación de base cero, el índice seleccionado pasado como argumento debe ser menor que la longitud del cssRules
objeto; de lo contrario, se generará un error.
Revisando la API CSSStyleDeclaration
Anteriormente expliqué cómo acceder a propiedades y valores individuales declarados como estilos en línea. Esto se hizo a través de element.style
, exponiendo la CSSStyleDeclaration
interfaz.
Sin embargo, la CSSStyleDeclaration
API también se puede exponer en una regla de estilo individual como un subconjunto de la CSSStyleSheet
API. Ya hice alusión a esto cuando le mostré cómo acceder a las propiedades dentro de una @keyframes
regla. Para entender cómo funciona esto, compare los siguientes dos fragmentos de código:
div/div
.box { color: lightblue; width: 100px; font-size: 1.3em !important;}
El primer ejemplo es un conjunto de estilos en línea a los que se puede acceder de la siguiente manera:
document.querySelector('div').style
Esto expone la CSSStyleDeclaration
API, que es lo que me permite hacer cosas como element.style.color
, element.style.width
etc.
Pero puedo exponer exactamente la misma API en una regla de estilo individual en una hoja de estilo externa. Esto significa que estoy combinando mi uso de la style
propiedad con la CSSStyleSheet
interfaz.
De este modo, se puede acceder al CSS del segundo ejemplo anterior, que utiliza exactamente los mismos estilos que la versión en línea:
document.styleSheets[0].cssRules[0].style
Esto abre un único CSSStyleDeclaration
objeto en una regla de estilo en la hoja de estilo. Si hubiera varias reglas de estilo, se podría acceder a cada una usando cssRules[1]
, cssRules[2]
, cssRules[3]
etc.
Deja una respuesta