Cómo obtener todas las propiedades personalizadas en una página en JavaScript
- ¿Qué estamos haciendo?
- Paso 1: obtén todas las hojas de estilo en una página
- Paso 2: descartar las hojas de estilo de terceros
- Paso 3: obtén todas las reglas para las hojas de estilo restantes
- Paso 4: descarta cualquier regla que no sea una regla de estilo básico
- Paso 5: obtenga el nombre y el valor de todas las propiedades
- Paso 6: descartar propiedades no personalizadas
- Paso 7: cree HTML para mostrar las muestras de color
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 única 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.
¿Qué sucede, sin embargo, 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'); // #00eb9bconst colorAccentSecondary = getComputedStyle(document.documentElement).getPropertyValue('--color-accent-secondary'); // #9db4ffconst colorAccentTertiary = getComputedStyle(document.documentElement).getPropertyValue('--color-accent-tertiary'); // #f2c0eaconst colorText = getComputedStyle(document.documentElement).getPropertyValue('--color-text'); // #292929const 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 que no es 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 bien, y funciona, si solo tenemos unas pocas propiedades personalizadas. He usado esta configuración en proyectos de producción antes. Pero también es posible automatizar esto.
Repasemos el proceso de automatización haciendo algo que funciona.
¿Qué 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.
Preparemos el escenario. Usaremos una lista desordenada para mostrar nuestra paleta. Cada muestra es un li
elemento que representaremos con JavaScript.
ul/ul
El CSS para el diseño de la cuadrícula no es relevante para la técnica de esta publicación, por lo que no lo veremos en detalle. Está disponible en la demostración de CodePen.
Ahora que tenemos implementaciones HTML y CSS, nos centraremos en JavaScript. Aquí hay un resumen de lo que haremos con nuestro código:
- Obtenga todas las hojas de estilo de 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.
- Descarte cualquier regla que no sea una regla de estilo básico.
- Obtenga el nombre y el valor de todas las propiedades CSS
- Descargar propiedades CSS no personalizadas
- Cree HTML para mostrar las muestras de color.
Hagámoslo.
Paso 1: obtén 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 los convertiremos en una matriz. También pongamos esto 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 serie de CSSStyleSheet
objetos, uno para cada hoja de estilo externa e interna en la página actual.
Paso 2: descartar 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, el acceso
cssRules
da como resultadoSecurityError
.
Eso significa que si la página actual enlaza con una hoja de estilo alojada en https://some-cdn.com, no podremos obtener propiedades personalizadas (ni ningún estilo) de ella. El enfoque que estamos adoptando aquí sólo funciona para hojas de estilo alojadas en el dominio actual.
CSSStyleSheet
Los objetos tienen una 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 una href
propiedad, pero el valor será null
.
Escribamos una función que descarta hojas de estilo de terceros. Lo haremos comparando el href
valor de la hoja de estilo con el archivo current location.origin
.
const isSameDomain = (styleSheet) = { if (!styleSheet.href) { return true; }
return styleSheet.href.indexOf(window.location.origin) === 0;};
Ahora lo usamos isSameDomain
como filtro en document.styleSheets
.
const getCSSCustomPropIndex = () = [...document.styleSheets] .filter(isSameDomain);
Demostración de CodePen
Una vez descartadas las hojas de estilo de terceros, podemos inspeccionar el contenido de las restantes.
Paso 3: obtén todas las reglas para las hojas de estilo restantes
Nuestro objetivo getCSSCustomPropIndex
es producir una serie 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 contiene cada regla 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 donde cada elemento de primer nivel sea lo que nos interesa. En este fragmento, iteramos sobre CSSStyleSheet
objetos individuales. Para cada uno de ellos, necesitamos su cssRules
. De los documentos de MDN:
La propiedad de solo lectura
CSSStyleSheet
cssRules devuelve un activoCSSRuleList
que proporciona una lista actualizada en tiempo real de cada regla CSS que compone la hoja de estilo. Cada elemento de la listaCSSRule
define una regla única.
Cada regla CSS es el selector, las llaves y las declaraciones de propiedad. Usamos el operador de extensión ...sheet.cssRules
para sacar todas las reglas del cssRules
objeto y colocarlas finalArr
. Cuando registramos la salida de getCSSCustomPropIndex
, obtenemos una matriz de CSSRule
objetos de un solo nivel.
Esto nos da todas las reglas CSS para todas las hojas de estilo. Queremos descartar algunos adelante de ellos, así que sigamos.
Paso 4: descarta cualquier regla que no sea una regla de estilo básico
Las reglas CSS vienen en diferentes tipos. Las especificaciones 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 Constantes de tipo de los documentos de MDN para CSSRule
obtener 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 de lado el CSSMediaRule
tipo de regla donde es válido 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 una type
propiedad que devuelve el número entero para ese tipo de constante. Usamos isStyleRule
para 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 entre ...sheet.cssRules
paréntesis para poder usar el filtro del método de matriz.
Nuestra hoja de estilo solo lo tenía, CSSStyleRules
por lo que los resultados de la demostración son los mismos que antes. Si nuestra hoja de estilo tuviera consultas o font-face
declaraciones de medios, isStyleRule
las descartaríamos.
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 un CSSStyleDeclaration
objeto. Se compone de propiedades CSS estándar, como color
, font-family
y border-radius
, además de propiedades personalizadas. Agreguemos eso a nuestra getCSSCustomPropIndex
función para que analice cada regla, construyendo una serie 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 de la reduce
función de devolución de llamada, tenemos una variable de marcador de posición, props
donde reuniremos las propiedades. La return
declaración combina la matriz de la iteración anterior (el acumulador) con la props
matriz actual.
En este momento, ambos son conjuntos vacíos. 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 colocar cada miembro en una matriz que recorremos con map. En la map
devolución de llamada, devolvemos una matriz con dos miembros. El primer miembro es ( que propName
incluye color
,,, etc.). El segundo miembro es el valor de cada propiedad. Para conseguirlo, utilizamos el método de . Toma un único parámetro, el nombre de cadena de la propiedad CSS.font-family
--color-accent
getPropertyValue
CSSStyleDeclaration
Usamos trim
tanto el nombre como el valor para asegurarnos de no incluir ningún espacio en blanco inicial o final que a veces se queda atrás.
Ahora, cuando invocamos getCSSCustomPropIndex
, obtenemos una serie de matrices. Cada matriz secundaria contiene un nombre de propiedad CSS y un valor.
¡Esto es lo que estamos buscando! Bueno, casi. Obtenemos todas las propiedades además de las propiedades personalizadas. Necesitamos un filtro más para eliminar esas propiedades estándar porque lo único que queremos son las propiedades personalizadas.
Paso 6: descartar propiedades no personalizadas
Para determinar si una propiedad es personalizada, podemos mirar el nombre. Sabemos que las propiedades personalizadas deben comenzar con dos guiones ( --
). Esto es único en el mundo CSS, por lo que podemos usarlo para escribir una función de filtro:
([propName]) = propName.indexOf("--") === 0)
Luego lo usamos como filtro en la props
matriz:
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 accesorios. A partir de ahí, comprobamos indexOf
el nombre de la propiedad. Si --
no está al principio del nombre del accesorio, no lo incluimos en la props
matriz.
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 del nivel 1 del modelo de objetos tipificados CSS que utiliza CSSStyleRule.styleMap
. La styleMap
propiedad es un objeto similar a una matriz de cada propiedad/valor de una regla CSS. Aún no lo tenemos, pero si lo tuviéramos, podríamos acortar nuestro código anterior eliminando map
:
// ...const props = [...rule.styleMap.entries()].filter(/*same filter*/);// ...
Demostración de CodePen
Al momento de escribir este artículo, Chrome y Edge tienen implementaciones, styleMap
pero ningún otro navegador importante las tiene. Debido a styleMap
que 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: cree HTML para mostrar las muestras de color
Lograr que los datos tuvieran la forma exacta que necesitábamos fue un trabajo duro. Necesitamos un poco más de JavaScript para representar nuestras hermosas muestras de color. En lugar de registrar la salida de getCSSCustomPropIndex
, almacenémosla en una variable.
const cssCustomPropIndex = getCSSCustomPropIndex();
Aquí está el HTML que utilizamos para crear nuestra muestra de color al comienzo de esta publicación:
ul/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 btoken interpolation"${val}"/b div input value="${prop}" readonly / input value="${val}" readonly / /div /li`, "");
Demostración de CodePen
Usamos reduce para iterar sobre el índice de propiedad personalizado y crear una única cadena con apariencia HTML para innerHTML
. Pero reduce
no es la única forma de hacerlo. Podríamos usar a map
y join
o forEach
. Cualquier método para construir la cadena funcionará aquí. Esta es simplemente mi forma preferida de hacerlo.
Quiero resaltar un par de fragmentos de código específicos. En la reduce
firma de devolución de llamada, utilizamos nuevamente la desestructuración de matrices con [prop, val]
, esta vez para acceder a ambos miembros de cada matriz secundaria. Luego usamos las variables prop
y val
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/b
Eso significa que terminamos con HTML que se ve así:
b/b
Pero, ¿cómo se establece eso un color de fondo? En el CSS completo usamos la propiedad personalizada --color
como valor de background-color
para cada uno .color__swatch
. Debido a 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 accesorios de colores personalizados. No hay ninguna razón por la que no podamos ampliar este enfoque para generar otras secciones de una biblioteca de patrones, como fuentes, espaciado, configuración de cuadrícula, etc. Cualquier cosa que pueda almacenarse como una propiedad personalizada se puede mostrar en una página automáticamente usando esta técnica.
Deja un comentario