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

Índice
  1. ¿Qué es el CSSOM?
  2. Estilos en línea a través de element.style
  3. La API de declaración CSSStyle
  4. setProperty(), getPropertyValue() y elemento()
  5. Usando removeProperty()
  6. Obtener y establecer la prioridad de una propiedad
  7. Acceder a las reglas @media con CSSOM
  8. Acceder a las reglas @keyframes con CSSOM
  9. Cómo agregar y eliminar declaraciones CSS
  10. 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 styleobjeto o propiedad, que está disponible en todos los elementos HTML. He aquí un ejemplo:

{ content: 'Example'; display: block; width: 50px;}

Aquí estoy agregando un ::beforepseudoelemento dentro del .boxelemento. 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 ::placeholderpseudo-elemento, que se aplica a inputlos 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 ::bananapseudoelemento 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 styleobjeto o usando getComputedStyle(), en ambos casos esas técnicas exponían la CSSStyleDeclarationinterfaz.

En otras palabras, las dos líneas siguientes devolverán un CSSStyleDeclarationobjeto 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 CSSStyleDeclarationobjeto 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 stylepropiedad, 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 styleobjeto:

  • 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 usando getComputedStyle(), que, como se mencionó, también expone un CSSStyleDeclarationobjeto.
  • 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 CSSStyleDeclarationobjeto. 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-sizeel 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 !importantpalabra 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 undefinedo 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 styleatributo 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 += `c​ode${i.selectorText}/c​odebr`;  }}

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 cssRulesobjeto 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_RULEconstante. 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 forbucle 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 :hoverestilos en mis enlaces y expando el selector para aplicar los mismos estilos a los elementos del :activeestado. Alternativamente, podría usar algún tipo de método de cadena o incluso una expresión regular para buscar todas las instancias de :hovery 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 @medialas 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 += `c​ode${j.selectorText}/c​odebr`;    }  }}

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 @mediaexiste alguna (tipo 4), recorro el cssRulesobjeto 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 @mediaregla es similar a la interfaz que se expone en una hoja de estilo. @mediaSin embargo, la regla también incluye una conditionTextpropiedad, 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 += `c​ode${i.conditionText}/c​odebr`;    // (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 mediaTextpropiedad 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 @mediaregla, consideremos cómo acceder a una @keyframesregla. 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 += `c​ode${j.keyText}/c​odebr`;    }  }}

En este ejemplo, busco reglas que tengan un tipo 7 (es decir, @keyframesreglas). Cuando encuentro una, reviso todas esas reglas cssRulesy registro la keyTextpropiedad para cada una. El registro en este caso será:

"0%""20%""100%"

Notarás que mi CSS original usa fromy tocomo el primer y último fotograma clave, pero la keyTextpropiedad los calcula como 0%y 100%. El valor de keyTexttambié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 @keyframesregla es name:

let myRules = document.styleSheets[0].cssRules,    p = document.querySelector('.output');for (i of myRules) {  if (i.type === 7) {    p.innerHTML += `c​ode${i.name}/c​odebr`;  }}

Recuerde que en CSS la @keyframesregla se ve así:

@keyframes exampleAnimation {  from {    color: blue;  }    20% {    color: orange;  }    to {    color: green;  }}

De esta forma, la namepropiedad me permite leer el nombre personalizado elegido para esa @keyframesregla. Este es el mismo nombre que se usaría en la animation-namepropiedad 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 += `c​ode${j.style.color}/c​odebr`;    }  }}

En este ejemplo, después de encontrar la @keyframesregla, 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 stylepropiedad individual. En este caso, dado que sé que colores 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 stylepropiedad 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 CSSStyleSheetinterfaz 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 cssRulespropiedad (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 cssRulespropiedad 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 cssRulesobjeto; 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 CSSStyleDeclarationinterfaz.

Sin embargo, la CSSStyleDeclarationAPI también se puede exponer en una regla de estilo individual como un subconjunto de la CSSStyleSheetAPI. Ya hice alusión a esto cuando le mostré cómo acceder a las propiedades dentro de una @keyframesregla. 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 CSSStyleDeclarationAPI, que es lo que me permite hacer cosas como element.style.color, element.style.widthetc.

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 stylepropiedad con la CSSStyleSheetinterfaz.

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 CSSStyleDeclarationobjeto 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.

SUSCRÍBETE A NUESTRO BOLETÍN 
No te pierdas de nuestro contenido ni de ninguna de nuestras guías para que puedas avanzar en los juegos que más te gustan.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir

Este sitio web utiliza cookies para mejorar tu experiencia mientras navegas por él. Este sitio web utiliza cookies para mejorar tu experiencia de usuario. Al continuar navegando, aceptas su uso. Mas informacion