Catégories
Astuces et Design

Comment obtenir toutes les propriétés personnalisées d'une page en JavaScript

Nous pouvons utiliser JavaScript pour obtenir la valeur d'une propriété personnalisée CSS. Robin a rédigé une explication détaillée à ce sujet dans Obtenir une valeur de propriété personnalisée CSS avec JavaScript. Pour vérifier, disons que nous avons déclaré une seule propriété personnalisée sur l'élément HTML:

html {
  --color-accent: #00eb9b;
}

En JavaScript, nous pouvons accéder à la valeur avec getComputedStyle et getPropertyValue:

const colorAccent = getComputedStyle(document.documentElement)
  .getPropertyValue('--color-accent'); // #00eb9b

Parfait. Nous avons maintenant accès à notre couleur d'accent en JavaScript. Tu sais ce qui est cool? Si nous changeons cette couleur en CSS, elle sera également mise à jour en JavaScript! Pratique.

Que se passe-t-il, cependant, quand ce n'est pas seulement une propriété à laquelle nous devons accéder en JavaScript, mais tout un tas d'entre eux?

html {
  --color-accent: #00eb9b;
  --color-accent-secondary: #9db4ff;
  --color-accent-tertiary: #f2c0ea;
  --color-text: #292929;
  --color-divider: #d7d7d7;
}

On se retrouve avec JavaScript qui ressemble à ceci:

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

Nous nous répétons beaucoup. Nous pourrions raccourcir chacune de ces lignes en faisant abstraction des tâches communes à une fonction.

const getCSSProp = (element, propName) => getComputedStyle(element).getPropertyValue(propName);
const colorAccent = getCSSProp(document.documentElement, '--color-accent'); // #00eb9b
// repeat for each custom property...

Cela aide à réduire la répétition du code, mais nous avons toujours une situation moins qu'idéale. Chaque fois que nous ajoutons une propriété personnalisée en CSS, nous devons écrire une autre ligne de JavaScript pour y accéder. Cela peut et fonctionne bien si nous n'avons que quelques propriétés personnalisées. J'ai déjà utilisé cette configuration sur des projets de production. Mais, il est également possible d'automatiser cela.

Passons en revue le processus d’automatisation en créant un outil fonctionnel.

Que faisons-nous?

Nous allons créer une palette de couleurs, qui est une caractéristique courante dans les bibliothèques de modèles. Nous générerons une grille d'échantillons de couleurs à partir de nos propriétés personnalisées CSS.

Voici la démo complète que nous allons construire étape par étape.

Un aperçu de notre palette de couleurs CSS basée sur les propriétés personnalisées. Affichage de six cartes, une pour chaque couleur, y compris le nom de la propriété personnalisée et la valeur hexadécimale dans chaque carte.
Voici ce que nous visons.

Préparons le terrain. Nous utiliserons une liste non ordonnée pour afficher notre palette. Chaque échantillon est un

  • élément que nous rendrons avec JavaScript.

      Le CSS pour la disposition de la grille n'est pas pertinent pour la technique de ce post, donc nous ne regarderons pas en détail. Il est disponible dans la démo CodePen.

      Maintenant que nous avons notre HTML et CSS en place, nous allons nous concentrer sur le JavaScript. Voici un aperçu de ce que nous allons faire avec notre code:

      1. Obtenez toutes les feuilles de style sur une page, à la fois externe et interne
      2. Supprimer toutes les feuilles de style hébergées sur des domaines tiers
      3. Obtenez toutes les règles pour les feuilles de style restantes
      4. Ignorez toutes les règles qui ne sont pas des règles de style de base
      5. Obtenez le nom et la valeur de toutes les propriétés CSS
      6. Ignorer les propriétés CSS non personnalisées
      7. Construisez du HTML pour afficher les échantillons de couleurs

      Allons-y.

      Étape 1: obtenir toutes les feuilles de style sur une page

      La première chose que nous devons faire est d'obtenir toutes les feuilles de style externes et internes sur la page actuelle. Les feuilles de style sont disponibles en tant que membres du document global.

      document.styleSheets

      Cela renvoie un objet de type tableau. Nous voulons utiliser des méthodes de tableau, nous allons donc le convertir en tableau. Mettons également cela dans une fonction que nous utiliserons tout au long de ce post.

      const getCSSCustomPropIndex = () => (...document.styleSheets);

      Quand on invoque getCSSCustomPropIndex, nous voyons un tableau de CSSStyleSheet objets, un pour chaque feuille de style externe et interne sur la page en cours.

      La sortie de getCSSCustomPropIndex, un tableau d'objets CSSStyleSheet

      Étape 2: supprimer les feuilles de style tierces

      Si notre script s'exécute sur https://example.com, toute feuille de style que nous voulons inspecter doit également être sur https://example.com. Il s'agit d'une fonction de sécurité. Depuis les documents MDN pour CSSStyleSheet:

      Dans certains navigateurs, si une feuille de style est chargée à partir d'un autre domaine, l'accès à cssRules résulte en SecurityError.

      Cela signifie que si la page actuelle est liée à une feuille de style hébergée sur https://some-cdn.com, nous ne pouvons pas obtenir de propriétés personnalisées – ou de styles – de celle-ci. L'approche que nous adoptons ici ne fonctionne que pour les feuilles de style hébergées sur le domaine actuel.

      CSSStyleSheet les objets ont un href propriété. Sa valeur est l'URL complète de la feuille de style, comme https://example.com/styles.css. Les feuilles de style internes ont un href mais la valeur sera null.

      Écrivons une fonction qui supprime les feuilles de style tierces. Nous le ferons en comparant les feuilles de style href valeur à la current location.origin.

      const isSameDomain = (styleSheet) => {
        if (!styleSheet.href) {
          return true;
        }
      

        return styleSheet.href.indexOf(window.location.origin) === 0;
      };

      Maintenant, nous utilisons isSameDomain comme filtre surdocument.styleSheets.

      const getCSSCustomPropIndex = () => (...document.styleSheets)
        .filter(isSameDomain);

      Une fois les feuilles de style tierces supprimées, nous pouvons inspecter le contenu de celles qui restent.

      Étape 3: Obtenez toutes les règles pour les feuilles de style restantes

      Notre objectif getCSSCustomPropIndex est de produire un tableau de tableaux. Pour y arriver, nous allons utiliser une combinaison de méthodes de tableau pour parcourir, trouver les valeurs que nous voulons et les combiner. Faisons un premier pas dans cette direction en produisant un tableau contenant toutes les règles de style.

      const getCSSCustomPropIndex = () => (...document.styleSheets)
        .filter(isSameDomain)
        .reduce((finalArr, sheet) => finalArr.concat(...sheet.cssRules), ());

      Nous utilisons reduce et concat parce que nous voulons produire un tableau plat où chaque élément de premier niveau est ce qui nous intéresse. Dans cet extrait, nous itérons sur chaque individu CSSStyleSheet objets. Pour chacun d'eux, nous avons besoin de son cssRules. Depuis les documents MDN:

      La lecture seule CSSStyleSheet la propriété cssRules renvoie un live CSSRuleList qui fournit une liste en temps réel et à jour de chaque règle CSS qui comprend la feuille de style. Chaque élément de la liste est un CSSRule définir une règle unique.

      Chaque règle CSS est le sélecteur, les accolades et les déclarations de propriété. Nous utilisons l'opérateur spread ...sheet.cssRules de retirer toutes les règles du cssRules objet et le placer dans finalArr. Lorsque nous enregistrons la sortie de getCSSCustomPropIndex, nous obtenons un tableau à un niveau de CSSRule objets.

      Exemple de sortie de getCSSCustomPropIndex produisant un tableau d'objets CSSRule

      Cela nous donne toutes les règles CSS pour toutes les feuilles de style. Nous voulons en éliminer certains, alors allons-y.

      Étape 4: supprimer toutes les règles qui ne sont pas des règles de style de base

      Les règles CSS sont de différents types. Les spécifications CSS définissent chacun des types avec un nom et un entier constants. Le type de règle le plus courant est le CSSStyleRule. Un autre type de règle est le CSSMediaRule. Nous les utilisons pour définir des requêtes multimédias, comme @media (min-width: 400px) {}. D'autres types incluent CSSSupportsRule, CSSFontFaceRule, et CSSKeyframesRule. Voir la section Constantes de type des documents MDN pour CSSRule pour la liste complète.

      Nous ne sommes intéressés que par les règles dans lesquelles nous définissons des propriétés personnalisées et, aux fins de cet article, nous nous concentrerons sur CSSStyleRule. Cela laisse de côté le CSSMediaRule type de règle où il est valide de définir des propriétés personnalisées. Nous pourrions utiliser une approche similaire à celle que nous utilisons pour extraire des propriétés personnalisées dans cette démonstration, mais nous exclurons ce type de règle spécifique pour limiter la portée de la démonstration.

      Pour limiter notre attention aux règles de style, nous allons écrire un autre filtre de tableau:

      const isStyleRule = (rule) => rule.type === 1;

      Chaque CSSRule a un type propriété qui renvoie l'entier pour cette constante de type. Nous utilisons isStyleRule à filtrer sheet.cssRules.

      const getCSSCustomPropIndex = () => (...document.styleSheets)
        .filter(isSameDomain)
        .reduce((finalArr, sheet) => finalArr.concat(
          (...sheet.cssRules).filter(isStyleRule)
        ), ());

      Une chose à noter est que nous emballons ...sheet.cssRules entre parenthèses afin que nous puissions utiliser le filtre de méthode de tableau.

      Notre feuille de style avait seulement CSSStyleRules donc les résultats de la démo sont les mêmes qu'avant. Si notre feuille de style contenait des requêtes multimédias ou font-face déclarations, isStyleRule les jetterait.

      Étape 5: obtenir le nom et la valeur de toutes les propriétés

      Maintenant que nous avons les règles que nous voulons, nous pouvons obtenir les propriétés qui les composent. CSSStyleRule les objets ont une propriété de style qui est un CSSStyleDeclaration objet. Il est composé de propriétés CSS standard, comme color, font-family, et border-radius, ainsi que des propriétés personnalisées. Ajoutons cela à notre getCSSCustomPropIndex fonction de sorte qu'il regarde chaque règle, en construisant un tableau de tableaux le long du chemin:

      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 nous l'invoquons maintenant, nous obtenons un tableau vide. Nous avons encore du travail à faire, mais cela jette les bases. Parce que nous voulons nous retrouver avec un tableau, nous commençons avec un tableau vide en utilisant l'accumulateur, qui est le deuxième paramètre de reduce. Dans le corps du reduce fonction de rappel, nous avons une variable d'espace réservé, props, où nous rassemblerons les propriétés. le return L'instruction combine le tableau de l'itération précédente – l'accumulateur – avec le courant props tableau.

      À l'heure actuelle, les deux sont des tableaux vides. Nous devons utiliser rule.style pour remplir les accessoires avec un tableau pour chaque propriété / valeur dans la règle actuelle:

      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);
            }, ())
        ), ());

      rule.style est semblable à un tableau, nous utilisons donc à nouveau l'opérateur d'étalement pour placer chaque membre de celui-ci dans un tableau que nous bouclons avec la carte. dans le map rappel, nous retournons un tableau avec deux membres. Le premier membre est propName (qui inclut color, font-family, --color-accent, etc.). Le deuxième membre est la valeur de chaque propriété. Pour l'obtenir, nous utilisons le getPropertyValue méthode de CSSStyleDeclaration. Il prend un seul paramètre, le nom de chaîne de la propriété CSS.

      Nous utilisons trim sur le nom et la valeur pour nous assurer que nous n'incluons aucun espace de début ou de fin qui est parfois laissé pour compte.

      Maintenant, quand nous invoquons getCSSCustomPropIndex, nous obtenons un tableau de tableaux. Chaque tableau enfant contient un nom de propriété CSS et une valeur.

      Sortie de getCSSCustomPropIndex montrant un tableau de tableaux contenant chaque nom et valeur de propriété

      C’est ce que nous recherchons! Enfin presque. Nous obtenons chaque propriété en plus des propriétés personnalisées. Nous avons besoin d'un filtre supplémentaire pour supprimer ces propriétés standard car tout ce que nous voulons, ce sont les propriétés personnalisées.

      Étape 6: supprimer les propriétés non personnalisées

      Pour déterminer si une propriété est personnalisée, nous pouvons regarder le nom. Nous savons que les propriétés personnalisées doivent commencer par deux tirets (--). C'est unique dans le monde CSS, nous pouvons donc l'utiliser pour écrire une fonction de filtre:

      ((propName)) => propName.indexOf("--") === 0)

      Ensuite, nous l'utilisons comme filtre sur le props tableau:

      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);
              }, ())
            ),
          ()
        );

      Dans la signature de fonction, nous avons ((propName)). Là, nous utilisons la déstructuration des tableaux pour accéder au premier membre de chaque tableau enfant dans les accessoires. De là, nous faisons un indexOf vérifier le nom de la propriété. Si -- n'est pas au début du nom de l'accessoire, nous ne l'incluons pas dans le props tableau.

      Lorsque nous enregistrons le résultat, nous avons la sortie exacte que nous recherchons: un tableau de tableaux pour chaque propriété personnalisée et sa valeur sans aucune autre propriété.

      La sortie de getCSSCustomPropIndex montrant un tableau de tableaux contenant chaque propriété personnalisée et sa valeur

      En regardant plus vers l'avenir, la création de la carte propriété / valeur ne nécessite pas autant de code. Il existe une alternative dans le brouillon du modèle d'objet typé CSS niveau 1 qui utilise CSSStyleRule.styleMap. le styleMap La propriété est un objet de type tableau de chaque propriété / valeur d'une règle CSS. Nous ne l'avons pas encore, mais si nous le faisions, nous pourrions raccourcir notre code ci-dessus en supprimant le map:

      // ...
      const props = (...rule.styleMap.entries()).filter(/*same filter*/);
      // ...

      Au moment d'écrire ces lignes, Chrome et Edge ont des implémentations de styleMap mais aucun autre navigateur majeur ne le fait. Parce que styleMap est dans un brouillon, il n'y a aucune garantie que nous l'obtiendrons réellement, et cela n'a aucun sens de l'utiliser pour cette démo. C'est quand même amusant de savoir que c'est une possibilité future!

      Nous avons la structure de données que nous voulons. Utilisons maintenant les données pour afficher des échantillons de couleurs.

      Étape 7: Créez du code HTML pour afficher les échantillons de couleurs

      Obtenir les données dans la forme exacte dont nous avions besoin a été un travail difficile. Nous avons besoin d'un peu plus de JavaScript pour rendre nos magnifiques échantillons de couleurs. Au lieu d'enregistrer la sortie de getCSSCustomPropIndex, enregistrons-le dans une variable.

      const cssCustomPropIndex = getCSSCustomPropIndex();

      Voici le code HTML que nous avons utilisé pour créer notre nuancier de couleurs au début de cet article:

        Nous utiliserons innerHTML pour remplir cette liste avec un élément de liste pour chaque couleur:

        document.querySelector(".colors").innerHTML = cssCustomPropIndex.reduce(
          (str, (prop, val)) => `${str}
      •        
                       
           
      • `,   "");

        Nous utilisons réduire pour itérer sur l'index d'accessoire personnalisé et construire une seule chaîne HTML pour innerHTML. Mais reduce n'est pas le seul moyen de le faire. Nous pourrions utiliser un map et join ou forEach. Toute méthode de construction de la chaîne fonctionnera ici. C'est juste ma façon préférée de le faire.

        Je veux mettre en évidence quelques bits de code spécifiques. dans le reduce signature de rappel, nous utilisons à nouveau la déstructuration des tableaux avec (prop, val), cette fois pour accéder aux deux membres de chaque tableau enfant. Nous utilisons ensuite le prop et val variables dans le corps de la fonction.

        Pour montrer l'exemple de chaque couleur, nous utilisons un b élément avec un style en ligne:

        Cela signifie que nous nous retrouvons avec du HTML qui ressemble à:

        Mais comment cela définit-il une couleur d'arrière-plan? Dans le CSS complet, nous utilisons la propriété personnalisée --color comme la valeur de background-color pour chaque .color__swatch. Étant donné que les règles CSS externes héritent des styles en ligne, --color est la valeur que nous fixons au b élément.

        .color__swatch {
          background-color: var(--color);
          /* other properties */
        }

        Nous avons maintenant un affichage HTML des échantillons de couleurs représentant nos propriétés personnalisées CSS!


        Cette démo se concentre sur les couleurs, mais la technique n'est pas limitée aux accessoires de couleur personnalisés. Il n'y a aucune raison que nous ne puissions pas étendre cette approche pour générer d'autres sections d'une bibliothèque de modèles, comme les polices, l'espacement, les paramètres de grille, etc. Tout ce qui pourrait être stocké en tant que propriété personnalisée peut être affiché sur une page automatiquement à l'aide de cette technique.

      • Laisser un commentaire

        Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *