Suchen Sie alle CSS-Regeln, die für ein Element gelten

87

Viele Tools / APIs bieten Möglichkeiten zur Auswahl von Elementen bestimmter Klassen oder IDs. Es ist auch möglich, die vom Browser geladenen Roh-Stylesheets zu überprüfen.

Damit Browser ein Element rendern können, kompilieren sie jedoch alle CSS-Regeln (möglicherweise aus verschiedenen Stylesheet-Dateien) und wenden sie auf das Element an. Dies sehen Sie mit Firebug oder dem WebKit Inspector - dem vollständigen CSS-Vererbungsbaum für ein Element.

Wie kann ich diese Funktion in reinem JavaScript reproduzieren, ohne zusätzliche Browser-Plugins zu benötigen?

Vielleicht kann ein Beispiel klarstellen, wonach ich suche:

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

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

Hier werden auf das p # -Beschreibungselement zwei CSS-Regeln angewendet: eine rote Farbe und eine Schriftgröße von 20 px.

Ich möchte die Quelle finden, aus der diese berechneten CSS-Regeln stammen (Farbe kommt die p-Regel und so weiter).

cgbystrom
quelle
Anzeigen in einem Browser und Benutzer der Browser-Entwicklertools (z. B. Registerkarte "Elemente" in Chrome)?
Ronnie Royston

Antworten:

77

Da diese Frage derzeit keine einfache (nicht bibliotheksbezogene) browserübergreifende Antwort enthält, werde ich versuchen, eine bereitzustellen:

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/

Beim Aufruf css(document.getElementById('elementId'))wird ein Array mit einem Element für jede CSS-Regel zurückgegeben, die dem übergebenen Element entspricht. Wenn Sie genauere Informationen zu den einzelnen Regeln erhalten möchten, lesen Sie die Dokumentation zum CSSRule-Objekt .

SB
quelle
1
a.matchesist in dieser Zeile definiert : a.matches = a.matches || a.webkitMatchesSelector || a.mozMatchesSelector || a.msMatchesSelector || a.oMatchesSelector. Wenn es bereits eine (Standard-) "Übereinstimmungs" -Methode für DOM-Knoten gibt, wird diese verwendet. Andernfalls wird versucht, die Webkit-spezifische Methode (webkitMatchesSelector) und dann die Mozilla-, Microsoft- und Opera-Methode zu verwenden. Sie können mehr darüber hier lesen: developer.mozilla.org/en/docs/Web/API/Element/matches
SB
3
Leider denke ich, dass diese Alternative nicht alle CSS-Regeln erkennt, die von übergeordneten Elementen in Kindern kaskadiert werden. Fiddle: jsfiddle.net/t554xo2L In diesem Fall stimmt die UL-Regel (die für das Element gilt) nicht mit der Schutzbedingung übereinif (a.matches(rules[r].selectorText)) .
Funforums
2
Ich habe nie behauptet, dass es / erbte / CSS-Regeln auflistet - alles, was es tut, ist, CSS-Regeln aufzulisten, die mit dem übergebenen Element übereinstimmen. Wenn Sie die geerbten Regeln auch für dieses Element erhalten möchten, müssen Sie wahrscheinlich das DOM nach oben durchlaufen und css()jedes der übergeordneten Elemente aufrufen .
SB
2
Ich weiß :-) Ich wollte nur darauf hinweisen, da Leute, die sich mit dieser Frage befassen könnten, annehmen könnten, dass sie "alle CSS-Regeln, die für ein Element gelten" erhalten, wie der Titel der Frage besagt, was nicht der Fall ist .
Funforums
3
Wenn Sie möchten, dass alle Regeln derzeit auf das Element angewendet werden, einschließlich der geerbten, sollten Sie getComputedStyle verwenden. In Anbetracht dessen denke ich, dass diese Antwort richtig ist und zu Recht keine von Eltern geerbten Stile enthält (z. B. dem Elternteil zugewiesene Textfarbe). Was jedoch nicht enthalten ist, sind Regeln, die bei Medienabfragen bedingt angewendet werden.
Tremby
23

BEARBEITEN: Diese Antwort ist jetzt veraltet und funktioniert in Chrome 64+ nicht mehr . Auf dem Weg zum historischen Kontext. Tatsächlich verweist dieser Fehlerbericht auf diese Frage, um alternative Lösungen zu finden.


Scheint, als hätte ich es geschafft, meine eigene Frage nach einer weiteren Stunde Recherche zu beantworten.

So einfach ist das:

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

(Funktioniert in WebKit / Chrome, möglicherweise auch in anderen)

cgbystrom
quelle
4
Nun, das ist nicht sehr nützlich, wenn es nur von Chrom unterstützt wird. Es wird für weniger als 5% aller Besucher funktionieren (abhängig von der Demografie).
Tomasi
5
@diamandiev: Ab Juni 2012 ist der Anteil der Chrome-Nutzung auf über 32% gestiegen (und etwas höher als die IE-Nutzung!). gs.statcounter.com
Roy Tinker
6
getMatchedCSSRules zeigt Ihnen NICHT die endgültigen Stile an, die für das Element gelten. Es gibt ein Array aller CSSStyleRule-Objekte zurück, die in der Reihenfolge gelten, in der sie angezeigt werden. Wenn Sie reaktionsschnelles Webdesign über CSS-Medienabfragen ausführen oder mehr als ein Stylesheet laden (wie eines für IE), müssen Sie dennoch jeden der zurückgegebenen Stile durchlaufen und die CSS-Spezifität für jede Regel berechnen. Berechnen Sie dann die endgültigen Regeln, die gelten. Sie müssen reproduzieren, was der Browser natürlich tut. Um dies in Ihrem Beispiel zu beweisen, stellen Sie "p {color: blue! Important}" an den Anfang Ihrer Stildeklaration.
mrbinky3000
24
Dies ist in Chrome 41 jetzt veraltet. Siehe code.google.com/p/chromium/issues/detail?id=437569#c2 .
Daniel Darabos
5
Dies wurde endlich in Chrome 63 (offizieller Blog-Beitrag - der auf diese Frage
zurückführt
18

Schauen Sie sich diese Bibliothek an, die genau das tut, wonach Sie gefragt wurden: http://www.brothercake.com/site/resources/scripts/cssutilities/

Es funktioniert in allen modernen Browsern bis hin zu IE6, bietet Ihnen Regel- und Eigenschaftssammlungen wie Firebug (tatsächlich ist es genauer als Firebug) und kann auch die relative oder absolute Spezifität einer Regel berechnen. Die einzige Einschränkung ist, dass es zwar statische Medientypen versteht, Medienabfragen jedoch nicht versteht.

Bruderkuchen
quelle
17

Kurzfassung 12. April 2017

Herausforderer erscheint.

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

Line erstellt /* 1 */ein flaches Array aller Regeln.
Linie/* 2 */ verwirft nicht übereinstimmende Regeln.

Basierend auf der Funktioncss(el) von @SB auf derselben Seite.

Beispiel 1

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

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

Mängel

  • Kein Medienhandling, nein @import,@media .
  • Kein Zugriff auf Stile, die aus domänenübergreifenden Stylesheets geladen wurden.
  • Keine Sortierung nach Selektor „Spezifität“ (Reihenfolge der Wichtigkeit).
  • Keine Stile von den Eltern geerbt.
  • Funktioniert möglicherweise nicht mit alten oder rudimentären Browsern.
  • Ich bin mir nicht sicher, wie es mit Pseudoklassen und Pseudoselektoren umgeht, aber es scheint in Ordnung zu sein.

Vielleicht werde ich eines Tages auf diese Mängel eingehen.

Langfassung 12. August 2018

Hier ist eine viel umfassendere Implementierung, die von einer GitHub-Seite einer anderen Person stammt (aus diesem Originalcode über Bugzilla gespalten ). Geschrieben für Gecko und IE, soll aber auch mit Blink funktionieren.

4. Mai 2017: Der Spezifitätsrechner hatte kritische Fehler, die ich jetzt behoben habe. (Ich kann die Autoren nicht benachrichtigen, da ich keinen GitHub-Account habe.)

12. August 2018: Die letzten Chrome-Updates scheinen den Objektbereich ( this) von Methoden entkoppelt zu haben , die unabhängigen Variablen zugewiesen wurden. Daher matcher(selector)funktioniert der Aufruf nicht mehr. Das Ersetzen durch matcher.call(el, selector)hat es gelöst.

// 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);
        };
}

Behobene Fehler

  • = 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
quelle
In getSheetRules musste ich if (stylesheet.cssRules === null) {return []} hinzufügen, damit es für mich funktioniert.
Gwater17
Testete die "lange Version". Funktioniert bei mir. Schade, dass getMatchedCSSRules () von Browsern nie standardisiert wurde.
Colin Moock
Wie geht das mit zwei Selektoren mit den gleichen Spezifitäten wie h1 und h1, div um - wo sollte der zuletzt deklarierte verwendet werden?
Stellan
4

Hier ist eine Version der Antwort von SB, die auch übereinstimmende Regeln in übereinstimmenden Medienabfragen zurückgibt. Ich habe die *.rules || *.cssRulesKoaleszenz und den .matchesImplementierungsfinder entfernt. Fügen Sie eine Polyfüllung hinzu oder fügen Sie diese Zeilen wieder hinzu, wenn Sie sie benötigen.

Diese Version gibt auch die zurück CSSStyleRule Objekte anstelle des Regeltextes zurück. Ich denke, dies ist etwas nützlicher, da die Besonderheiten der Regeln auf diese Weise leichter programmgesteuert überprüft werden können.

Kaffee:

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
quelle
Wie könnte dies geändert werden, um es auch bei Kindern der Verstorbenen elementanzuwenden?
Kragalon
2
Was ist Ihr Anwendungsfall? Ich sehe nicht wirklich, wo das nützlich wäre, da Regeln, die für Kinder gelten, nicht unbedingt für die Eltern gelten. Sie würden einfach mit einem Stapel von Regeln enden, die nichts Besonderes gemeinsam haben. Wenn Sie das wirklich wollen, können Sie einfach über untergeordnete Elemente zurückgreifen und diese Methode für jedes ausführen und ein Array aller Ergebnisse erstellen.
tremby
Ich versuche nur, cloneNode(true)Funktionalität zu schaffen , aber mit tief geklontem Styling.
Kragalon
1
Diese Bedingung: if (window.matchMedia (rule.conditionText) .matches) {...} hat in meinem Fall eine Übereinstimmung verhindert, da "rule.conditionText" undefiniert war. Ohne es hat es funktioniert. Sie können dies auf news.ycombinator.com testen . "span.pagetop b" hat eine Medienabfrageregel, die nicht mit Ihrer Funktion übereinstimmt.
Ayal Gelles
1
Chrome unterstützt die conditionText-Eigenschaft für CSSMediaRule-Instanzen nicht.
Macil
3

Hier ist meine Version der getMatchedCSSRulesFunktion, die @mediaAbfrage unterstützt.

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
quelle
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
quelle
3
Sinnloses Duplikat einer alten Version meiner Antwort. Verschmutzen Sie einfach die Seite. Vollständige und aktuelle Version: hier .
7vujy0f0hy
1

Um IE9 + zu gewährleisten, habe ich eine Funktion geschrieben, die CSS für das angeforderte Element und seine untergeordneten Elemente berechnet und die Möglichkeit bietet, es bei Bedarf im folgenden Snippet unter einem neuen Klassennamen zu speichern.

/**
  * @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;
}

Verwendung

getElementStyles(document.getElementByClassName('.my-class'), '.dummy-class', 'width:100%;opaity:0.5;transform:scale(1.5);');
Shobhit Sharma
quelle
2
1. Sie können das gesamte computeStylesUnterprogramm durch einfach ersetzen el => getComputedStyle(el).cssText. Beweis: Geige . 2. '.' + element.className ist eine fehlerhafte Konstruktion, da davon ausgegangen wird, dass ein Klassenname vorhanden ist. Gültige Konstruktion ist element.className.replace(/^| /g, '.'). 3. Ihre Funktion ignoriert die Möglichkeit anderer CSS-Selektoren als nur Klassen. 4. Ihre Rekursion ist willkürlich auf eine Ebene beschränkt (Kinder, aber keine Enkelkinder). 5. Verwendung: es keine getElementByClassName, nur getElementsByClassName(gibt einen Array).
7vujy0f0hy
1

Ich denke, die Antwort von SB sollte zu diesem Zeitpunkt die akzeptierte sein, aber sie ist nicht genau. Es wird einige Male erwähnt, dass es einige Regeln geben wird, die möglicherweise übersehen werden. Vor diesem Hintergrund habe ich mich entschieden, document.querySelectorAll anstelle von element.matches zu verwenden. Das einzige ist, dass Sie eine eindeutige Identifizierung von Elementen benötigen, um sie mit der gesuchten zu vergleichen. In den meisten Fällen denke ich, dass dies erreichbar ist, indem die ID auf einen eindeutigen Wert festgelegt wird. Auf diese Weise können Sie das übereinstimmende Element identifizieren, das Ihnen gehört. Wenn Sie sich eine allgemeine Möglichkeit vorstellen können, das Ergebnis von document.querySelectorAll mit dem gesuchten Element abzugleichen, wäre dies im Wesentlichen eine vollständige Polyfüllung von getMatchedCSSRules.

Ich habe die Leistung für document.querySelectorAll überprüft, da es wahrscheinlich langsamer als element.matches ist, aber in den meisten Fällen sollte es kein Problem sein. Ich sehe, dass es ungefähr 0,001 Millisekunden dauert.

Ich habe auch eine CSSUtilities-Bibliothek gefunden, die ankündigt, dass dies möglich ist, aber ich fühle mich alt und wurde seit einiger Zeit nicht mehr aktualisiert. Wenn ich mir den Quellcode anschaue, denke ich, dass es Fälle gibt, in denen er fehlt.

cagdas_ucar
quelle