
Podemos usar JavaScript para obtener el valor de una propiedad personalizada de CSS. Robin escribió una explicación detallada sobre esto en Obtener un valor de propiedad personalizado de CSS con JavaScript. Para revisar, digamos que hemos declarado una sola propiedad personalizada en el elemento HTML:
html {
--color-accent: #00eb9b;
}
En JavaScript, podemos acceder al valor con getComputedStyle
y getPropertyValue
:
const colorAccent = getComputedStyle(document.documentElement)
.getPropertyValue('--color-accent'); // #00eb9b
Perfecto. Ahora tenemos acceso a nuestro color de acento en JavaScript. ¿Sabes qué es genial? Si cambiamos ese color en CSS, ¡también se actualiza en JavaScript! Práctico.
Sin embargo, ¿qué sucede cuando no es solo una propiedad a la que necesitamos acceder en JavaScript, sino un montón de ellas?
html {
--color-accent: #00eb9b;
--color-accent-secondary: #9db4ff;
--color-accent-tertiary: #f2c0ea;
--color-text: #292929;
--color-divider: #d7d7d7;
}
Terminamos con JavaScript que se ve así:
const colorAccent = getComputedStyle(document.documentElement).getPropertyValue('--color-accent'); // #00eb9b
const colorAccentSecondary = getComputedStyle(document.documentElement).getPropertyValue('--color-accent-secondary'); // #9db4ff
const colorAccentTertiary = getComputedStyle(document.documentElement).getPropertyValue('--color-accent-tertiary'); // #f2c0ea
const colorText = getComputedStyle(document.documentElement).getPropertyValue('--color-text'); // #292929
const colorDivider = getComputedStyle(document.documentElement).getPropertyValue('--color-text'); // #d7d7d7
Nos repetimos mucho. Podríamos acortar cada una de estas líneas abstrayendo las tareas comunes a una función.
const getCSSProp = (element, propName) => getComputedStyle(element).getPropertyValue(propName);
const colorAccent = getCSSProp(document.documentElement, '--color-accent'); // #00eb9b
// repeat for each custom property...
Eso ayuda a reducir la repetición de código, pero todavía tenemos una situación menos que ideal. Cada vez que agregamos una propiedad personalizada en CSS, tenemos que escribir otra línea de JavaScript para acceder a ella. Esto puede funcionar y funciona bien si solo tenemos algunas propiedades personalizadas. He usado esta configuración en proyectos de producción antes. Pero también es posible automatizar esto.
Analicemos el proceso de automatización haciendo algo que funcione.
Que estamos haciendo
Crearemos una paleta de colores, que es una característica común en las bibliotecas de patrones. Generaremos una cuadrícula de muestras de color a partir de nuestras propiedades personalizadas de CSS.
Aquí está la demostración completa que crearemos paso a paso.
Esto es lo que pretendemos.
Preparemos el escenario. Usaremos una lista desordenada para mostrar nuestra paleta. Cada muestra es una <li>
elemento que renderizaremos con JavaScript.
<ul class="colors"></ul>
El CSS para el diseño de la cuadrícula no es pertinente para la técnica en esta publicación, por lo que no lo veremos en detalle. Está disponible en la demostración de CodePen.
Ahora que tenemos nuestro HTML y CSS en su lugar, nos centraremos en JavaScript. Aquí hay un resumen de lo que haremos con nuestro código:
- Obtenga todas las hojas de estilo en una página, tanto externas como internas
- Descarte cualquier hoja de estilo alojada en dominios de terceros
- Obtenga todas las reglas para las hojas de estilo restantes
- Descarta las reglas que no sean reglas de estilo básicas.
- Obtenga el nombre y el valor de todas las propiedades CSS
- Descartar propiedades CSS no personalizadas
- Construya HTML para mostrar las muestras de color
Hagámoslo.
Paso 1: obtenga todas las hojas de estilo en una página
Lo primero que debemos hacer es obtener todas las hojas de estilo internas y externas en la página actual. Las hojas de estilo están disponibles como miembros del documento global.
document.styleSheets
Eso devuelve un objeto similar a una matriz. Queremos usar métodos de matriz, así que lo convertiremos en una matriz. Pongamos esto también en una función que usaremos a lo largo de esta publicación.
const getCSSCustomPropIndex = () => [...document.styleSheets];
Demostración de CodePen
Cuando invocamos getCSSCustomPropIndex
, vemos una matriz de CSSStyleSheet
objetos, uno para cada hoja de estilo externa e interna en la página actual.
Paso 2: descarte las hojas de estilo de terceros
Si nuestro script se ejecuta en https://example.com, cualquier hoja de estilo que queramos inspeccionar también debe estar en https://example.com. Esta es una característica de seguridad. De los documentos de MDN para CSSStyleSheet
:
En algunos navegadores, si se carga una hoja de estilo desde un dominio diferente, acceder cssRules
resultados en SecurityError
.
Eso significa que si la página actual se vincula a una hoja de estilo alojada en https://some-cdn.com, no podemos obtener propiedades personalizadas, ni ningún estilo, de ella. El enfoque que estamos adoptando aquí solo funciona para hojas de estilo alojadas en el dominio actual.
CSSStyleSheet
los objetos tienen un href
propiedad. Su valor es la URL completa de la hoja de estilo, como https://example.com/styles.css. Las hojas de estilo internas tienen un href
propiedad, pero el valor será null
.
Escribamos una función que descarte hojas de estilo de terceros. Lo haremos comparando las hojas de estilo. href
valor para el current location.origin
.
const isSameDomain = (styleSheet) => {
if (!styleSheet.href) {
return true;
}
return styleSheet.href.indexOf(window.location.origin) === 0;
};
Ahora usamos isSameDomain
como un filtro endocument.styleSheets
.
const getCSSCustomPropIndex = () => [...document.styleSheets]
.filter(isSameDomain);
Demostración de CodePen
Con las hojas de estilo de terceros descartadas, podemos inspeccionar el contenido de las restantes.
Paso 3: obtenga todas las reglas para las hojas de estilo restantes
Nuestro objetivo para getCSSCustomPropIndex
es producir una matriz de matrices. Para llegar allí, usaremos una combinación de métodos de matriz para recorrer, encontrar los valores que queremos y combinarlos. Demos un primer paso en esa dirección produciendo una matriz que contenga todas las reglas de estilo.
const getCSSCustomPropIndex = () => [...document.styleSheets]
.filter(isSameDomain)
.reduce((finalArr, sheet) => finalArr.concat(...sheet.cssRules), []);
Demostración de CodePen
Usamos reduce
y concat
porque queremos producir una matriz plana en la que cada elemento de primer nivel sea lo que nos interese. En este fragmento, iteramos sobre CSSStyleSheet
objetos. Para cada uno de ellos, necesitamos su cssRules
. De los documentos de MDN:
El de solo lectura CSSStyleSheet
propiedad cssRules devuelve una CSSRuleList
que proporciona una lista actualizada en tiempo real de todas las reglas CSS que componen la hoja de estilo. Cada elemento de la lista es un CSSRule
definiendo una sola regla.
Cada regla CSS es el selector, las llaves y las declaraciones de propiedad. Usamos el operador de propagación ...sheet.cssRules
para eliminar todas las reglas del cssRules
objeto y colóquelo en finalArr
. Cuando registramos la salida de getCSSCustomPropIndex
, obtenemos una matriz de un solo nivel de CSSRule
objetos.
Esto nos da todas las reglas CSS para todas las hojas de estilo. Queremos descartar algunos de esos, así que sigamos adelante.
Paso 4: descarte las reglas que no sean reglas de estilo básicas
Las reglas CSS vienen en diferentes tipos. Las especificaciones de CSS definen cada uno de los tipos con un nombre constante y un número entero. El tipo de regla más común es la CSSStyleRule
. Otro tipo de regla es la CSSMediaRule
. Los usamos para definir consultas de medios, como @media (min-width: 400px) {}
. Otros tipos incluyen CSSSupportsRule
, CSSFontFaceRule
, y CSSKeyframesRule
. Consulte la sección de constantes de tipo de los documentos de MDN para CSSRule
para la lista completa.
Solo nos interesan las reglas en las que definimos propiedades personalizadas y, para los fines de esta publicación, nos centraremos en CSSStyleRule
. Eso deja fuera el CSSMediaRule
tipo de regla donde sea válido para definir propiedades personalizadas. Podríamos usar un enfoque similar al que estamos usando para extraer propiedades personalizadas en esta demostración, pero excluiremos este tipo de regla específica para limitar el alcance de la demostración.
Para limitar nuestro enfoque a las reglas de estilo, escribiremos otro filtro de matriz:
const isStyleRule = (rule) => rule.type === 1;
Cada CSSRule
tiene un type
propiedad que devuelve el número entero para ese tipo constante. Usamos isStyleRule
Filtrar sheet.cssRules
.
const getCSSCustomPropIndex = () => [...document.styleSheets]
.filter(isSameDomain)
.reduce((finalArr, sheet) => finalArr.concat(
[...sheet.cssRules].filter(isStyleRule)
), []);
Demostración de CodePen
Una cosa a tener en cuenta es que estamos envolviendo ...sheet.cssRules
entre paréntesis para que podamos usar el filtro del método de matriz.
Nuestra hoja de estilo solo tenía CSSStyleRules
por lo que los resultados de la demostración son los mismos que antes. Si nuestra hoja de estilo tiene consultas de medios o font-face
declaraciones, isStyleRule
los descartaría.
Paso 5: obtenga el nombre y el valor de todas las propiedades
Ahora que tenemos las reglas que queremos, podemos obtener las propiedades que las componen. CSSStyleRule
Los objetos tienen una propiedad de estilo que es CSSStyleDeclaration
objeto. Se compone de propiedades CSS estándar, como color
, font-family
, y border-radius
, además de propiedades personalizadas. Agreguemos eso a nuestro getCSSCustomPropIndex
función para que observe todas las reglas, construyendo una matriz de matrices a lo largo del camino:
const getCSSCustomPropIndex = () => [...document.styleSheets]
.filter(isSameDomain)
.reduce((finalArr, sheet) => finalArr.concat(
[...sheet.cssRules]
.filter(isStyleRule)
.reduce((propValArr, rule) => {
const props = []; /* TODO: more work needed here */
return [...propValArr, ...props];
}, [])
), []);
Si invocamos esto ahora, obtenemos una matriz vacía. Tenemos más trabajo por hacer, pero esto sienta las bases. Como queremos terminar con una matriz, comenzamos con una matriz vacía usando el acumulador, que es el segundo parámetro de reduce
. En el cuerpo del reduce
función de devolución de llamada, tenemos una variable de marcador de posición, props
, donde recogeremos las propiedades. El return
declaración combina la matriz de la iteración anterior – el acumulador – con el actual props
formación.
En este momento, ambos son matrices vacías. Necesitamos usar rule.style
para completar los accesorios con una matriz para cada propiedad / valor en la regla actual:
const getCSSCustomPropIndex = () => [...document.styleSheets]
.filter(isSameDomain)
.reduce((finalArr, sheet) => finalArr.concat(
[...sheet.cssRules]
.filter(isStyleRule)
.reduce((propValArr, rule) => {
const props = [...rule.style].map((propName) => [
propName.trim(),
rule.style.getPropertyValue(propName).trim()
]);
return [...propValArr, ...props];
}, [])
), []);
Demostración de CodePen
rule.style
es similar a una matriz, por lo que usamos el operador de extensión nuevamente para poner cada miembro en una matriz que recorremos con map. En el map
devolución de llamada, devolvemos una matriz con dos miembros. El primer miembro es propName
(que incluye color
, font-family
, --color-accent
, etc.). El segundo miembro es el valor de cada propiedad. Para conseguir eso, usamos el getPropertyValue
método de CSSStyleDeclaration
. Toma un solo parámetro, el nombre de cadena de la propiedad CSS.
Usamos trim
tanto en el nombre como en el valor para asegurarnos de que no incluimos ningún espacio en blanco inicial o final que a veces se quede atrás.
Ahora cuando invocamos getCSSCustomPropIndex
, obtenemos una matriz de matrices. Cada matriz secundaria contiene un nombre de propiedad CSS y un valor.
¡Esto es lo que estamos buscando! Bueno, casi. Estamos obteniendo todas las propiedades además de las propiedades personalizadas. Necesitamos un filtro más para eliminar esas propiedades estándar porque todo lo que queremos son las propiedades personalizadas.
Paso 6: descarte las propiedades no personalizadas
Para determinar si una propiedad es personalizada, podemos mirar el nombre. Sabemos que las propiedades personalizadas deben comenzar con dos guiones (--
). Eso es único en el mundo de CSS, por lo que podemos usarlo para escribir una función de filtro:
([propName]) => propName.indexOf("--") === 0)
Luego lo usamos como filtro en el props
formación:
const getCSSCustomPropIndex = () =>
[...document.styleSheets].filter(isSameDomain).reduce(
(finalArr, sheet) =>
finalArr.concat(
[...sheet.cssRules].filter(isStyleRule).reduce((propValArr, rule) => {
const props = [...rule.style]
.map((propName) => [
propName.trim(),
rule.style.getPropertyValue(propName).trim()
])
.filter(([propName]) => propName.indexOf("--") === 0);
return [...propValArr, ...props];
}, [])
),
[]
);
Demostración de CodePen
En la firma de la función, tenemos ([propName])
. Allí, estamos usando la desestructuración de matrices para acceder al primer miembro de cada matriz secundaria en props. A partir de ahí, hacemos un indexOf
Verifique el nombre de la propiedad. Si --
no está al principio del nombre del accesorio, entonces no lo incluimos en el props
formación.
Cuando registramos el resultado, tenemos el resultado exacto que estamos buscando: una matriz de matrices para cada propiedad personalizada y su valor sin otras propiedades.
Mirando más hacia el futuro, la creación del mapa de propiedad / valor no tiene por qué requerir tanto código. Hay una alternativa en el borrador de nivel 1 del modelo de objetos con tipo CSS que usa CSSStyleRule.styleMap
. El styleMap
property es un objeto en forma de matriz de cada propiedad / valor de una regla CSS. Todavía no lo tenemos, pero si lo tuviéramos, podríamos acortar nuestro código anterior eliminando el map
:
// ...
const props = [...rule.styleMap.entries()].filter(/*same filter*/);
// ...
Demostración de CodePen
En el momento de escribir este artículo, Chrome y Edge tienen implementaciones de styleMap
pero ningún otro navegador importante lo hace. Porque styleMap
está en un borrador, no hay garantía de que realmente lo obtengamos, y no tiene sentido usarlo para esta demostración. Aún así, ¡es divertido saber que es una posibilidad futura!
Tenemos la estructura de datos que queremos. Ahora usemos los datos para mostrar muestras de color.
Paso 7: compile HTML para mostrar las muestras de color
Conseguir que los datos tuvieran la forma exacta que necesitábamos fue un trabajo duro. Necesitamos un poco más de JavaScript para renderizar nuestras hermosas muestras de color. En lugar de registrar la salida de getCSSCustomPropIndex
, vamos a almacenarlo en variable.
const cssCustomPropIndex = getCSSCustomPropIndex();
Aquí está el HTML que usamos para crear nuestra muestra de color al comienzo de esta publicación:
<ul class="colors"></ul>
Usaremos innerHTML
para completar esa lista con un elemento de lista para cada color:
document.querySelector(".colors").innerHTML = cssCustomPropIndex.reduce(
(str, [prop, val]) => `${str}<li class="color">
<b class="color__swatch" style="--color: ${val}"></b>
<div class="color__details">
<input value="${prop}" readonly />
<input value="${val}" readonly />
</div>
</li>`,
"");
Demostración de CodePen
Usamos reduce para iterar sobre el índice de prop personalizado y construir una única cadena de aspecto HTML para innerHTML
. Pero reduce
no es la única forma de hacer esto. Nos vendría bien un map
y join
o forEach
. Cualquier método de construcción de la cadena funcionará aquí. Esta es solo mi forma preferida de hacerlo.
Quiero resaltar un par de bits específicos de código. En el reduce
firma de devolución de llamada, estamos usando la desestructuración de matrices nuevamente con [prop, val]
, esta vez para acceder a ambos miembros de cada matriz secundaria. Luego usamos el prop
y val
variables en el cuerpo de la función.
Para mostrar el ejemplo de cada color, usamos un b
elemento con un estilo en línea:
<b class="color__swatch" style="--color: ${val}"></b>
Eso significa que terminamos con HTML que se ve así:
<b class="color__swatch" style="--color: #00eb9b"></b>
Pero, ¿cómo establece eso un color de fondo? En el CSS completo usamos la propiedad personalizada --color
como el valor de background-color
para cada .color__swatch
. Dado que las reglas CSS externas heredan de los estilos en línea, --color
es el valor que establecemos en el b
elemento.
.color__swatch {
background-color: var(--color);
/* other properties */
}
¡Ahora tenemos una visualización HTML de muestras de color que representan nuestras propiedades personalizadas de CSS!
Esta demostración se centra en los colores, pero la técnica no se limita a los accesorios de color personalizados. No hay ninguna razón por la que no podamos expandir este enfoque para generar otras secciones de una biblioteca de patrones, como fuentes, espaciado, configuraciones de cuadrícula, etc. Todo lo que pueda almacenarse como una propiedad personalizada se puede mostrar en una página automáticamente usando esta técnica.