Cómo obtener todas las propiedades personalizadas en una página en JavaScript

Índice
  1. ¿Qué estamos haciendo?
  2. Paso 1: obtén todas las hojas de estilo en una página
  3. Paso 2: descartar las hojas de estilo de terceros
  4. Paso 3: obtén todas las reglas para las hojas de estilo restantes
  5. Paso 4: descarta cualquier regla que no sea una regla de estilo básico
  6. Paso 5: obtenga el nombre y el valor de todas las propiedades
  7. Paso 6: descartar propiedades no personalizadas
  8. 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 getComputedStyley 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 lielemento 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:

  1. Obtenga todas las hojas de estilo de una página, tanto externas como internas.
  2. Descarte cualquier hoja de estilo alojada en dominios de terceros.
  3. Obtenga todas las reglas para las hojas de estilo restantes.
  4. Descarte cualquier regla que no sea una regla de estilo básico.
  5. Obtenga el nombre y el valor de todas las propiedades CSS
  6. Descargar propiedades CSS no personalizadas
  7. 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 CSSStyleSheetobjetos, 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 cssRulesda como resultado SecurityError.

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.

CSSStyleSheetLos objetos tienen una hrefpropiedad. Su valor es la URL completa de la hoja de estilo, como https://example.com/styles.css. Las hojas de estilo internas tienen una hrefpropiedad, 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 isSameDomaincomo 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 getCSSCustomPropIndexes 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 reducey concatporque queremos producir una matriz plana donde cada elemento de primer nivel sea lo que nos interesa. En este fragmento, iteramos sobre CSSStyleSheetobjetos individuales. Para cada uno de ellos, necesitamos su cssRules. De los documentos de MDN:

La propiedad de solo lectura CSSStyleSheetcssRules devuelve un activo CSSRuleListque proporciona una lista actualizada en tiempo real de cada regla CSS que compone la hoja de estilo. Cada elemento de la lista CSSRuledefine una regla única.

Cada regla CSS es el selector, las llaves y las declaraciones de propiedad. Usamos el operador de extensión ...sheet.cssRulespara sacar todas las reglas del cssRulesobjeto y colocarlas finalArr. Cuando registramos la salida de getCSSCustomPropIndex, obtenemos una matriz de CSSRuleobjetos 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, CSSFontFaceRuley CSSKeyframesRule. Consulte la sección Constantes de tipo de los documentos de MDN para CSSRuleobtener 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 CSSMediaRuletipo 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 CSSRuletiene una typepropiedad que devuelve el número entero para ese tipo de constante. Usamos isStyleRulepara 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.cssRulesparéntesis para poder usar el filtro del método de matriz.

Nuestra hoja de estilo solo lo tenía, CSSStyleRulespor lo que los resultados de la demostración son los mismos que antes. Si nuestra hoja de estilo tuviera consultas o font-facedeclaraciones de medios, isStyleRulelas 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. CSSStyleRuleLos objetos tienen una propiedad de estilo que es un CSSStyleDeclarationobjeto. Se compone de propiedades CSS estándar, como color, font-familyy border-radius, además de propiedades personalizadas. Agreguemos eso a nuestra getCSSCustomPropIndexfunció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 reducefunción de devolución de llamada, tenemos una variable de marcador de posición, propsdonde reuniremos las propiedades. La returndeclaración combina la matriz de la iteración anterior (el acumulador) con la propsmatriz actual.

En este momento, ambos son conjuntos vacíos. Necesitamos usar rule.stylepara 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.stylees 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 mapdevolución de llamada, devolvemos una matriz con dos miembros. El primer miembro es ( que propNameincluye 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-accentgetPropertyValueCSSStyleDeclaration

Usamos trimtanto 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 propsmatriz:

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 indexOfel nombre de la propiedad. Si --no está al principio del nombre del accesorio, no lo incluimos en la propsmatriz.

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 styleMappropiedad 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, styleMappero ningún otro navegador importante las tiene. Debido a styleMapque 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 innerHTMLpara 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 reduceno es la única forma de hacerlo. Podríamos usar a mapy joino 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 reducefirma 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 propy valen el cuerpo de la función.

Para mostrar el ejemplo de cada color, usamos un belemento 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 --colorcomo valor de background-colorpara cada uno .color__swatch. Debido a que las reglas CSS externas heredan de los estilos en línea, --colores el valor que establecemos en el belemento.

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

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 un comentario

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