Trouvez toutes les règles CSS qui s'appliquent à un élément

87

De nombreux outils / API permettent de sélectionner des éléments de classes ou d'identifiants spécifiques. Il est également possible d'inspecter les feuilles de style brutes chargées par le navigateur.

Cependant, pour que les navigateurs rendent un élément, ils compileront toutes les règles CSS (éventuellement à partir de différents fichiers de feuille de style) et les appliqueront à l'élément. C'est ce que vous voyez avec Firebug ou WebKit Inspector - l'arbre d'héritage CSS complet pour un élément.

Comment puis-je reproduire cette fonctionnalité en JavaScript pur sans avoir besoin de plugins de navigateur supplémentaires?

Un exemple peut peut-être clarifier ce que je recherche:

<style type="text/css">
    p { color :red; }
    #description { font-size: 20px; }
</style>

<p id="description">Lorem ipsum</p>

Ici, l'élément de description p # a deux règles CSS appliquées: une couleur rouge et une taille de police de 20 px.

Je voudrais trouver la source d'où proviennent ces règles CSS calculées (la couleur vient de la règle p et ainsi de suite).

cgbystrom
la source
Afficher dans un navigateur et utiliser les outils de développement du navigateur (par exemple, onglet Éléments dans Chrome)?
Ronnie Royston

Réponses:

77

Étant donné que cette question n'a actuellement pas de réponse légère (non-bibliothèque) et compatible avec tous les navigateurs, je vais essayer d'en fournir une:

function css(el) {
    var sheets = document.styleSheets, ret = [];
    el.matches = el.matches || el.webkitMatchesSelector || el.mozMatchesSelector 
        || el.msMatchesSelector || el.oMatchesSelector;
    for (var i in sheets) {
        var rules = sheets[i].rules || sheets[i].cssRules;
        for (var r in rules) {
            if (el.matches(rules[r].selectorText)) {
                ret.push(rules[r].cssText);
            }
        }
    }
    return ret;
}

JSFiddle: http://jsfiddle.net/HP326/6/

L'appel css(document.getElementById('elementId'))renverra un tableau avec un élément pour chaque règle CSS qui correspond à l'élément passé. Si vous souhaitez obtenir des informations plus spécifiques sur chaque règle, consultez la documentation de l' objet CSSRule .

SB
la source
1
a.matchesest défini dans cette ligne: a.matches = a.matches || a.webkitMatchesSelector || a.mozMatchesSelector || a.msMatchesSelector || a.oMatchesSelector. Cela signifie que s'il existe déjà une méthode (standard) "matches" pour les nœuds DOM, il l'utilisera, sinon il essaiera d'utiliser la méthode spécifique à Webkit (webkitMatchesSelector), puis celles de Mozilla, Microsoft et Opera. Vous pouvez en savoir plus ici: developer.mozilla.org/en/docs/Web/API/Element/matches
SB
3
Malheureusement, je pense que cette alternative ne détecte pas toutes les règles CSS qui découlent des éléments parents des enfants. Fiddle: jsfiddle.net/t554xo2L Dans ce cas, la règle UL (qui s'applique à l'élément) ne correspond pas à la if (a.matches(rules[r].selectorText))condition de protection.
funforums
2
Je n'ai jamais prétendu avoir répertorié / hérité / règles CSS - tout ce qu'il fait est de lister les règles CSS qui correspondent à l'élément passé. Si vous souhaitez également obtenir les règles héritées pour cet élément, vous devez probablement parcourir le DOM vers le haut et appeler css()chacun des éléments parents.
SB
2
Je sais :-) Je voulais juste le souligner car les personnes qui pourraient se pencher sur cette question pourraient supposer qu'elle obtient `` toutes les règles css qui s'appliquent à un élément '', comme le dit le titre de la question, ce qui n'est pas le cas .
funforums
3
Si vous souhaitez que toutes les règles actuellement appliquées à l'élément, y compris celles héritées, vous devez utiliser getComputedStyle. À la lumière de cela, je pense que cette réponse est correcte et a raison de ne pas inclure les styles hérités des parents (couleur du texte attribuée au parent, par exemple). Ce qu'il n'inclut pas, cependant, ce sont les règles appliquées de manière conditionnelle avec les requêtes multimédias.
tremby
23

MODIFIER: Cette réponse est désormais obsolète et ne fonctionne plus dans Chrome 64+ . Partir pour le contexte historique. En fait, ce rapport de bogue renvoie à cette question pour des solutions alternatives à son utilisation.


Il semble que j'ai réussi à répondre à ma propre question après une autre heure de recherche.

C'est aussi simple que ça:

window.getMatchedCSSRules(document.getElementById("description"))

(Fonctionne dans WebKit / Chrome, peut-être d'autres aussi)

cgbystrom
la source
4
Eh bien, ce n'est pas très utile s'il n'est pris en charge que par Chrome. Cela fonctionnera pour moins de 5% de tous les visiteurs (selon la démographie).
Tomasi
5
@diamandiev: Depuis juin 2012, la part d'utilisation de Chrome est passée à plus de 32% (et est légèrement supérieure à l'utilisation d'IE!). gs.statcounter.com
Roy Tinker
6
getMatchedCSSRules ne vous montre PAS les styles finaux qui s'appliquent à l'élément. Il renvoie un tableau de tous les objets CSSStyleRule qui s'appliquent dans l'ordre dans lequel ils apparaissent. Si vous effectuez une conception Web réactive via des requêtes multimédias CSS ou si vous chargez plusieurs feuilles de style (comme une pour IE), vous devez toujours parcourir chacun des styles renvoyés et calculer la spécificité css pour chaque règle. Calculez ensuite les règles finales qui s'appliquent. Vous devez reproduire naturellement ce que fait le navigateur. Pour le prouver dans votre exemple, ajoutez «p {color: blue! Important}» au début de votre déclaration de style.
mrbinky3000
24
Cette fonctionnalité est désormais obsolète dans Chrome 41. Voir code.google.com/p/chromium/issues/detail?id=437569#c2 .
Daniel Darabos
5
Cela a finalement été supprimé dans Chrome 63 (article de blog officiel - qui renvoie à cette question)
brichins
19

Jetez un œil à cette bibliothèque, qui fait ce qui a été demandé: http://www.brothercake.com/site/resources/scripts/cssutilities/

Il fonctionne dans tous les navigateurs modernes jusqu'à IE6, peut vous donner des collections de règles et de propriétés comme Firebug (en fait, il est plus précis que Firebug), et peut également calculer la spécificité relative ou absolue de n'importe quelle règle. La seule mise en garde est que, bien qu'il comprenne les types de médias statiques, il ne comprend pas les requêtes de média.

petit gâteau
la source
Ce module est vraiment génial, j'espère juste qu'il obtiendra plus d'amour de l'auteur.
mr1031011 le
17

Version courte 12 avril 2017

Challenger apparaît.

var getMatchedCSSRules = (el, css = el.ownerDocument.styleSheets) => 
    [].concat(...[...css].map(s => [...s.cssRules||[]])) /* 1 */
    .filter(r => el.matches(r.selectorText));            /* 2 */

Line /* 1 */crée un tableau plat de toutes les règles.
La ligne /* 2 */ignore les règles qui ne correspondent pas.

Basé sur la fonctioncss(el) de @SB sur la même page.

Exemple 1

var div = iframedoc.querySelector("#myelement");
var rules = getMatchedCSSRules(div, iframedoc.styleSheets);
console.log(rules[0].parentStyleSheet.ownerNode, rules[0].cssText);

Exemple 2

var getMatchedCSSRules = (el, css = el.ownerDocument.styleSheets) => 
    [].concat(...[...css].map(s => [...s.cssRules||[]]))
    .filter(r => el.matches(r.selectorText));

function Go(big,show) {
    var r = getMatchedCSSRules(big);
PrintInfo:
    var f = (dd,rr,ee="\n") => dd + rr.cssText.slice(0,50) + ee;
    show.value += "--------------- Rules: ----------------\n";
    show.value += f("Rule 1:   ", r[0]);
    show.value += f("Rule 2:   ", r[1]);
    show.value += f("Inline:   ", big.style);
    show.value += f("Computed: ", getComputedStyle(big), "(…)\n");
    show.value += "-------- Style element (HTML): --------\n";
    show.value += r[0].parentStyleSheet.ownerNode.outerHTML;
}

Go(...document.querySelectorAll("#big,#show"));
.red {color: red;}
#big {font-size: 20px;}
<h3 id="big" class="red" style="margin: 0">Lorem ipsum</h3>
<textarea id="show" cols="70" rows="10"></textarea>

Les lacunes

  • Aucune manipulation médiatique, non @import, @media.
  • Aucun accès aux styles chargés à partir de feuilles de style inter-domaines.
  • Pas de tri par sélecteur «spécificité» (ordre d'importance).
  • Aucun style hérité des parents.
  • Peut ne pas fonctionner avec les navigateurs anciens ou rudimentaires.
  • Je ne sais pas comment il gère les pseudo-classes et les pseudo-sélecteurs, mais semble bien se comporter.

Je vais peut-être aborder ces lacunes un jour.

Version longue 12 août 2018

Voici une implémentation beaucoup plus complète tirée de la page GitHub de quelqu'un (dérivée de ce code original , via Bugzilla ). Écrit pour Gecko et IE, mais il aurait également fonctionné avec Blink.

4 mai 2017: Le calculateur de spécificité a eu des bogues critiques que j'ai maintenant corrigés. (Je ne peux pas avertir les auteurs car je n'ai pas de compte GitHub.)

12 août 2018: les mises à jour récentes de Chrome semblent avoir découplé la portée de l'objet ( this) des méthodes attribuées à des variables indépendantes. Par conséquent, l'invocation matcher(selector)a cessé de fonctionner. Le remplacer par l' matcher.call(el, selector)a résolu.

// polyfill window.getMatchedCSSRules() in FireFox 6+
if (typeof window.getMatchedCSSRules !== 'function') {
    var ELEMENT_RE = /[\w-]+/g,
            ID_RE = /#[\w-]+/g,
            CLASS_RE = /\.[\w-]+/g,
            ATTR_RE = /\[[^\]]+\]/g,
            // :not() pseudo-class does not add to specificity, but its content does as if it was outside it
            PSEUDO_CLASSES_RE = /\:(?!not)[\w-]+(\(.*\))?/g,
            PSEUDO_ELEMENTS_RE = /\:\:?(after|before|first-letter|first-line|selection)/g;
        // convert an array-like object to array
        function toArray(list) {
            return [].slice.call(list);
        }

        // handles extraction of `cssRules` as an `Array` from a stylesheet or something that behaves the same
        function getSheetRules(stylesheet) {
            var sheet_media = stylesheet.media && stylesheet.media.mediaText;
            // if this sheet is disabled skip it
            if ( stylesheet.disabled ) return [];
            // if this sheet's media is specified and doesn't match the viewport then skip it
            if ( sheet_media && sheet_media.length && ! window.matchMedia(sheet_media).matches ) return [];
            // get the style rules of this sheet
            return toArray(stylesheet.cssRules);
        }

        function _find(string, re) {
            var matches = string.match(re);
            return matches ? matches.length : 0;
        }

        // calculates the specificity of a given `selector`
        function calculateScore(selector) {
            var score = [0,0,0],
                parts = selector.split(' '),
                part, match;
            //TODO: clean the ':not' part since the last ELEMENT_RE will pick it up
            while (part = parts.shift(), typeof part == 'string') {
                // find all pseudo-elements
                match = _find(part, PSEUDO_ELEMENTS_RE);
                score[2] += match;
                // and remove them
                match && (part = part.replace(PSEUDO_ELEMENTS_RE, ''));
                // find all pseudo-classes
                match = _find(part, PSEUDO_CLASSES_RE);
                score[1] += match;
                // and remove them
                match && (part = part.replace(PSEUDO_CLASSES_RE, ''));
                // find all attributes
                match = _find(part, ATTR_RE);
                score[1] += match;
                // and remove them
                match && (part = part.replace(ATTR_RE, ''));
                // find all IDs
                match = _find(part, ID_RE);
                score[0] += match;
                // and remove them
                match && (part = part.replace(ID_RE, ''));
                // find all classes
                match = _find(part, CLASS_RE);
                score[1] += match;
                // and remove them
                match && (part = part.replace(CLASS_RE, ''));
                // find all elements
                score[2] += _find(part, ELEMENT_RE);
            }
            return parseInt(score.join(''), 10);
        }

        // returns the heights possible specificity score an element can get from a give rule's selectorText
        function getSpecificityScore(element, selector_text) {
            var selectors = selector_text.split(','),
                selector, score, result = 0;
            while (selector = selectors.shift()) {
                if (matchesSelector(element, selector)) {
                    score = calculateScore(selector);
                    result = score > result ? score : result;
                }
            }
            return result;
        }

        function sortBySpecificity(element, rules) {
            // comparing function that sorts CSSStyleRules according to specificity of their `selectorText`
            function compareSpecificity (a, b) {
                return getSpecificityScore(element, b.selectorText) - getSpecificityScore(element, a.selectorText);
            }

            return rules.sort(compareSpecificity);
        }

        // Find correct matchesSelector impl
        function matchesSelector(el, selector) {
          var matcher = el.matchesSelector || el.mozMatchesSelector || 
              el.webkitMatchesSelector || el.oMatchesSelector || el.msMatchesSelector;
          return matcher.call(el, selector);
        }

        //TODO: not supporting 2nd argument for selecting pseudo elements
        //TODO: not supporting 3rd argument for checking author style sheets only
        window.getMatchedCSSRules = function (element /*, pseudo, author_only*/) {
            var style_sheets, sheet, sheet_media,
                rules, rule,
                result = [];
            // get stylesheets and convert to a regular Array
            style_sheets = toArray(window.document.styleSheets);

            // assuming the browser hands us stylesheets in order of appearance
            // we iterate them from the beginning to follow proper cascade order
            while (sheet = style_sheets.shift()) {
                // get the style rules of this sheet
                rules = getSheetRules(sheet);
                // loop the rules in order of appearance
                while (rule = rules.shift()) {
                    // if this is an @import rule
                    if (rule.styleSheet) {
                        // insert the imported stylesheet's rules at the beginning of this stylesheet's rules
                        rules = getSheetRules(rule.styleSheet).concat(rules);
                        // and skip this rule
                        continue;
                    }
                    // if there's no stylesheet attribute BUT there IS a media attribute it's a media rule
                    else if (rule.media) {
                        // insert the contained rules of this media rule to the beginning of this stylesheet's rules
                        rules = getSheetRules(rule).concat(rules);
                        // and skip it
                        continue
                    }

                    // check if this element matches this rule's selector
                    if (matchesSelector(element, rule.selectorText)) {
                        // push the rule to the results set
                        result.push(rule);
                    }
                }
            }
            // sort according to specificity
            return sortBySpecificity(element, result);
        };
}

Correction de bugs

  • = match+= match
  • return re ? re.length : 0;return matches ? matches.length : 0;
  • _matchesSelector(element, selector)matchesSelector(element, selector)
  • matcher(selector)matcher.call(el, selector)
7vujy0f0hy
la source
Dans getSheetRules, j'ai dû ajouter if (stylesheet.cssRules === null) {return []} pour que cela fonctionne pour moi.
Gwater17
Testé la «version longue». Travaille pour moi. Dommage que getMatchedCSSRules () n'ait jamais été standardisé par les navigateurs.
colin moock
Comment cela gère-t-il deux sélecteurs avec les mêmes spécificités comme, h1 et h1, div - où celui déclaré en dernier devrait être utilisé?
Stellan
Pouvons-nous avoir une idée de la gestion des pseudo ici? github.com/dvtng/jss/blob/master/jss.js
mr1031011
4

Voici une version de la réponse de SB qui renvoie également des règles de correspondance dans les requêtes multimédias correspondantes. J'ai supprimé la *.rules || *.cssRulescoalescence et l' .matchesoutil de recherche d'implémentation; ajoutez un polyfill ou rajoutez ces lignes si vous en avez besoin.

Cette version renvoie également les CSSStyleRuleobjets plutôt que le texte de la règle. Je pense que c'est un peu plus utile, car les spécificités des règles peuvent être plus facilement explorées par programme de cette façon.

Café:

getMatchedCSSRules = (element) ->
  sheets = document.styleSheets
  matching = []

  loopRules = (rules) ->
    for rule in rules
      if rule instanceof CSSMediaRule
        if window.matchMedia(rule.conditionText).matches
          loopRules rule.cssRules
      else if rule instanceof CSSStyleRule
        if element.matches rule.selectorText
          matching.push rule
    return

  loopRules sheet.cssRules for sheet in sheets

  return matching

JS:

function getMatchedCSSRules(element) {
  var i, len, matching = [], sheets = document.styleSheets;

  function loopRules(rules) {
    var i, len, rule;

    for (i = 0, len = rules.length; i < len; i++) {
      rule = rules[i];
      if (rule instanceof CSSMediaRule) {
        if (window.matchMedia(rule.conditionText).matches) {
          loopRules(rule.cssRules);
        }
      } else if (rule instanceof CSSStyleRule) {
        if (element.matches(rule.selectorText)) {
          matching.push(rule);
        }
      }
    }
  };

  for (i = 0, len = sheets.length; i < len; i++) {
    loopRules(sheets[i].cssRules);
  }

  return matching;
}
tremby
la source
Comment cela pourrait-il être changé pour être utilisé également sur les enfants des décédés element?
Kragalon le
2
Quel est votre cas d'utilisation? Je ne vois pas vraiment où cela serait utile, car les règles qui s'appliquent aux enfants ne s'appliquent pas nécessairement au parent. Vous vous retrouveriez juste avec un tas de règles sans rien de particulier en commun. Si vous voulez vraiment que vous puissiez simplement répéter les enfants et exécuter cette méthode pour chacun, et créer un tableau de tous les résultats.
tremby
J'essaie simplement de créer des cloneNode(true)fonctionnalités, mais avec un style également cloné en profondeur.
Kragalon
1
cette condition: if (window.matchMedia (rule.conditionText) .matches) {...} a empêché une correspondance dans mon cas puisque "rule.conditionText" n'était pas défini. Sans cela, cela a fonctionné. Vous pouvez essayer et tester ceci sur news.ycombinator.com . "span.pagetop b" a une règle de requête multimédia qui ne correspond pas à votre fonction telle quelle.
ayal gelles
1
Chrome ne prend pas en charge la propriété conditionText sur les instances CSSMediaRule.
Macil le
3

Voici ma version de la getMatchedCSSRulesfonction qui prend en charge la @mediarequête.

const getMatchedCSSRules = (el) => {
  let rules = [...document.styleSheets]
  rules = rules.filter(({ href }) => !href)
  rules = rules.map((sheet) => [...(sheet.cssRules || sheet.rules || [])].map((rule) => {
    if (rule instanceof CSSStyleRule) {
      return [rule]
    } else if (rule instanceof CSSMediaRule && window.matchMedia(rule.conditionText)) {
      return [...rule.cssRules]
    }
    return []
  }))
  rules = rules.reduce((acc, rules) => acc.concat(...rules), [])
  rules = rules.filter((rule) => el.matches(rule.selectorText))
  rules = rules.map(({ style }) => style)
  return rules
}
user3896501
la source
1

var GetMatchedCSSRules = (elem, css = document.styleSheets) => Array.from(css)
  .map(s => Array.from(s.cssRules).filter(r => elem.matches(r.selectorText)))
  .reduce((a,b) => a.concat(b));

function Go(paragraph, print) {
  var rules = GetMatchedCSSRules(paragraph);
PrintInfo:
  print.value += "Rule 1: " + rules[0].cssText + "\n";
  print.value += "Rule 2: " + rules[1].cssText + "\n\n";
  print.value += rules[0].parentStyleSheet.ownerNode.outerHTML;
}

Go(document.getElementById("description"), document.getElementById("print"));
p {color: red;}
#description {font-size: 20px;}
<p id="description">Lorem ipsum</p>
<textarea id="print" cols="50" rows="12"></textarea>

Thomas
la source
3
Copie inutile d'une ancienne version de ma réponse. Juste polluer la page. Version complète et à jour: ici .
7vujy0f0hy
1

Assurant IE9 +, j'ai écrit une fonction qui calcule le CSS pour l'élément demandé et ses enfants, et donne la possibilité de l'enregistrer dans un nouveau nom de classe si nécessaire dans l'extrait ci-dessous.

/**
  * @function getElementStyles
  *
  * Computes all CSS for requested HTMLElement and its child nodes and applies to dummy class
  *
  * @param {HTMLElement} element
  * @param {string} className (optional)
  * @param {string} extras (optional)
  * @return {string} CSS Styles
  */
function getElementStyles(element, className, addOnCSS) {
  if (element.nodeType !== 1) {
    return;
  }
  var styles = '';
  var children = element.getElementsByTagName('*');
  className = className || '.' + element.className.replace(/^| /g, '.');
  addOnCSS = addOnCSS || '';
  styles += className + '{' + (window.getComputedStyle(element, null).cssText + addOnCSS) + '}';
  for (var j = 0; j < children.length; j++) {
    if (children[j].className) {
      var childClassName = '.' + children[j].className.replace(/^| /g, '.');
      styles += ' ' + className + '>' + childClassName +
        '{' + window.getComputedStyle(children[j], null).cssText + '}';
    }
  }
  return styles;
}

Usage

getElementStyles(document.getElementByClassName('.my-class'), '.dummy-class', 'width:100%;opaity:0.5;transform:scale(1.5);');
Shobhit Sharma
la source
2
1. Vous pouvez remplacer tout le computeStylessous-programme par simplement el => getComputedStyle(el).cssText. Preuve: violon . 2. '.' + element.className est une construction défectueuse car elle suppose l'existence d'un nom de classe. La construction valide est element.className.replace(/^| /g, '.'). 3. Votre fonction ignore la possibilité d'autres sélecteurs CSS que de simples classes. 4. Votre récursivité est arbitrairement limitée à un niveau (enfants mais pas petits-enfants). 5. Utilisation: il n'y a pas getElementByClassName, seulement getElementsByClassName(renvoie un tableau).
7vujy0f0hy
1

Je pense que la réponse de SB devrait être acceptée à ce stade, mais elle n'est pas exacte. Il est mentionné à quelques reprises que certaines règles pourraient être manquées. Face à cela, j'ai décidé d'utiliser document.querySelectorAll au lieu de element.matches. La seule chose est que vous auriez besoin d'une sorte d'identification unique des éléments pour le comparer à celui que vous recherchez. Dans la plupart des cas, je pense que cela est réalisable en définissant son identifiant pour avoir une valeur unique. C'est ainsi que vous pouvez identifier l'élément correspondant comme étant le vôtre. Si vous pouvez penser à un moyen général de faire correspondre le résultat de document.querySelectorAll à l'élément que vous recherchez, ce serait essentiellement un polyfill complet de getMatchedCSSRules.

J'ai vérifié les performances de document.querySelectorAll car il est probablement plus lent que element.matches mais dans la plupart des cas, cela ne devrait pas poser de problème. Je vois que cela prend environ 0,001 milliseconde.

J'ai également trouvé la bibliothèque CSSUtilities qui annonce qu'elle peut le faire mais je pense qu'elle est ancienne et qu'elle n'a pas été mise à jour depuis un moment. En regardant son code source, cela me fait penser qu'il peut y avoir des cas où il manque.

cagdas_ucar
la source
CSSUtilities est vraiment ancien, mais il renvoie également les règles pour les pseudo états (par exemple, il peut renvoyer des règles de survol). Je n'ai pas encore trouvé de réponse concernant le pseudo-état.
mr1031011 le