97

Many tools/APIs provide ways of selecting elements of specific classes or IDs. There's also possible to inspect the raw stylesheets loaded by the browser.

However, for browsers to render an element, they'll compile all CSS rules (possibly from different stylesheet files) and apply it to the element. This is what you see with Firebug or the WebKit Inspector - the full CSS inheritance tree for an element.

How can I reproduce this feature in pure JavaScript without requiring additional browser plugins?

Perhaps an example can provide some clarification for what I'm looking for:

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

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

Here the p#description element have two CSS rules applied: a red color and a font size of 20 px.

I would like to find the source from where these computed CSS rules originate from (color comes the p rule and so on).

cgbystrom
  • 1,927
  • 1
  • 14
  • 11
  • Good answers also at [Is it possible to find CSS rules from an HTML node via JavaScript?](http://stackoverflow.com/questions/4482374/is-it-possible-to-find-css-rules-from-an-html-node-via-javascript) – Bergi Sep 24 '13 at 13:31
  • View in a browser and user the browser developer tools (e.g. Elements tab in Chrome)? – Ronnie Royston Sep 08 '18 at 18:45

10 Answers10

89

Since this question currently doesn't have a lightweight (non-library), cross-browser compatible answer, I'll try to provide one:

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/

Calling css(document.getElementById('elementId')) will return an array with an element for each CSS rule that matches the passed element. If you want to find out more specific information about each rule, check out the CSSRule object documentation.

Kerem
  • 11,377
  • 5
  • 59
  • 58
S.B.
  • 2,920
  • 1
  • 17
  • 25
  • this misses few "a."-s – makc Apr 25 '14 at 21:36
  • Where is this .matches coming from? It appears to work in the fiddle (jQuery 1.10) but I can't find any documentation of that method, and it is not there in my case (jQuery 1.11) I replaced a.matches with a.is and it works like a charm. – funforums May 05 '15 at 16:20
  • 2
    `a.matches` is defined in this line: `a.matches = a.matches || a.webkitMatchesSelector || a.mozMatchesSelector || a.msMatchesSelector || a.oMatchesSelector`. It means, if there already is a (standard) "matches" method for DOM Nodes, it will use that one, otherwise it tries to use the Webkit specific one (webkitMatchesSelector), then the Mozilla, Microsoft and Opera ones. You can read more about it here: https://developer.mozilla.org/en/docs/Web/API/Element/matches – S.B. May 05 '15 at 19:49
  • Thanks, I was convinced it was a JQuery method, I don't know why. – funforums May 06 '15 at 09:29
  • 6
    Unfortunately, I think this alternative does not detect all the CSS rules that cascade from parent elements in children. Fiddle: http://jsfiddle.net/t554xo2L/ In this case, the UL rule (which applies to the element) is not matched into the `if (a.matches(rules[r].selectorText))` guarding condition. – funforums May 06 '15 at 10:50
  • 4
    I never claimed that it listed /inherited/ CSS rules - all it does is list CSS rules that match the passed element. If you want to get the inherited rules for that element as well, you probably need to traverse the DOM upwards and call `css()` on each of the parent elements. – S.B. May 06 '15 at 12:58
  • 3
    I know :-) I just wanted to point this out since people that could look into this question might assume that it gets 'all css rules that apply to an element', as the title of the question says, which it is not the case. – funforums May 06 '15 at 13:38
  • 3
    If you want all rules currently applied to the element including inherited ones, you should be using getComputedStyle. In light of that, I think this answer is correct and is right not to include styles inherited from parents (text colour assigned to the parent, for example). What it doesn't include, though, are rules applied conditionally with media queries. – tremby Mar 21 '16 at 08:21
  • See my answer for a version which also handles media queries. – tremby Mar 21 '16 at 08:43
  • Since Chrome 64 throws a DOMException when one tries to access `sheets[i].rules` and it is not there, we now need to wrap in a try..catch block. See: https://jsfiddle.net/ebohp6mt/2/ – desm Feb 09 '18 at 18:51
  • Now that `getMatchedCSSRules` is finally gone, this should be the accepted answer. However be careful here as now with Shadow DOM `document` may not be the root node of `el`. Would recommend a check along the lines of `var doc = el.getRootNode && el.getRootNode() || document;` – Andrew Feb 27 '18 at 13:00
  • Getting a `Uncaught DOMException: Failed to read the 'rules' property from 'CSSStyleSheet': Cannot access rules` in Chrome v79, but this might be because I'm trying to change a css property of an iframe pointing to another website. – Cyril Duchon-Doris Jan 08 '20 at 15:13
  • how and where should, someone do put this code? in what type or format I should send the element to function? – Farhang Amaji May 20 '22 at 14:58
27

Short version12 April 2017

Challenger appears.

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

Line /* 1 */ builds a flat array of all rules.
Line /* 2 */ discards non-matching rules.

Based on function css(el) by @S.B. on the same page.

Example 1

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

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

Shortcomings

  • No media handling, no @import, @media.
  • No access to styles loaded from cross-domain stylesheets.
  • No sorting by selector “specificity” (order of importance).
  • No styles inherited from parents.
  • May not work with old or rudimentary browsers.
  • Not sure how it copes with pseudo-classes and pseudo-selectors but seems to fare okay.

Maybe I will address these shortcomings one day.

Long version12 August 2018

Here’s a much more comprehensive implementation taken from someone’s GitHub page (forked from this original code, via Bugzilla). Written for Gecko and IE, but is rumoured to work also with Blink.

4 May 2017: The specificity calculator has had critical bugs which I have now fixed. (I can’t notify the authors because I don’t have a GitHub account.)

12 August 2018: Recent Chrome updates seem to have decoupled object scope (this) from methods assigned to independent variables. Therefore invocation matcher(selector) has stopped working. Replacing it by matcher.call(el, selector) has solved it.

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

Fixed 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
  • 8,741
  • 1
  • 28
  • 33
  • In getSheetRules I had to add if(stylesheet.cssRules === null) { return [] } to get it to work for me. – Gwater17 Aug 16 '18 at 21:15
  • Tested the "Long version." Works for me. Too bad getMatchedCSSRules() was never standardized by browsers. – colin moock Nov 09 '19 at 17:25
  • How does this handle two selectors with the same specificities like, h1 and h1, div - where the one declarated last should be used? – Stellan Apr 08 '20 at 18:59
  • PErhaps we can get some idea for handling pseudo here? https://github.com/dvtng/jss/blob/master/jss.js – mr1031011 Sep 25 '20 at 14:24
23

EDIT: This answer is now deprecated and no longer works in Chrome 64+. Leaving for historical context. In fact that bug report links back to this question for alternative solutions to using this.


Seems I managed to answer my own question after another hour of research.

It's as simple as this:

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

(Works in WebKit/Chrome, possibly others too)

aug
  • 11,138
  • 9
  • 72
  • 93
cgbystrom
  • 1,927
  • 1
  • 14
  • 11
  • 4
    Well this isnt of much use if it is supported only by chrome. It will work for less than 5% of all visitors (depending on demographics). – Tomasi Sep 15 '10 at 21:06
  • 5
    @diamandiev: As of June 2012, Chrome usage share has increased to over 32% (and is slightly higher than IE usage!). http://gs.statcounter.com/ – Roy Tinker Jun 25 '12 at 19:28
  • 6
    getMatchedCSSRules does NOT show you the final styles that apply to the element. It returns an array of all the CSSStyleRule objects that apply in the order in which they appear. If you do responsive web design via CSS media queries or load more than one style sheet (like one for IE), you still need to loop through each of the styles returned and compute the css specificity for each rule. Then compute the final rules that apply. You need to reproduce what the browser does naturally. To prove this in your example, prepend "p {color: blue !important}" to the start of your style declaration. – mrbinky3000 Jul 25 '12 at 17:41
  • Does anyone know of a way of getting all CSSStyleRole objects that apply to an element, that works in Firefox/IE/possibly other browsers too? – heyman Aug 07 '12 at 16:28
  • 1
    Note this only works for rules within stylesheets sent from the same server as the HTML – jscripter Mar 22 '14 at 08:33
  • 27
    This is now deprecated in Chrome 41. See https://code.google.com/p/chromium/issues/detail?id=437569#c2. – Daniel Darabos Feb 18 '15 at 14:23
  • @cgbystrom In Chrome 53, `getMatchedCSSRules` issues a deprecation warning and returns null. Therefore, this should no longer be the accepted answer. –  Sep 16 '16 at 14:06
  • As others have said, this is deprecated. It has a myriad of inconsistencies across browsers and is not reliable. https://bugs.chromium.org/p/chromium/issues/detail?id=437569#c2 – Wes Jan 12 '17 at 21:50
  • 5
    This has finally been [removed in Chrome 63](https://developers.google.com/web/updates/2017/10/chrome-63-deprecations) (official blog post - which points back to this question) – brichins Oct 20 '17 at 22:47
  • still working on Version 63.0.3239.84 (Official Build) Built on Ubuntu , running on Ubuntu 16.04 (64-bit) – Aryeh Armon Jan 03 '18 at 12:57
  • 2
    Deprecation was pushed back to [Chrome 64](https://bugs.chromium.org/p/chromium/issues/detail?id=437569&desc=2). It's finally gone now. – Andrew Feb 27 '18 at 12:56
  • chrome says : window.getMatchedCSSRules is not a function – pixparker May 04 '18 at 07:57
22

Have a look at this library, which does what was asked for: http://www.brothercake.com/site/resources/scripts/cssutilities/

It works in all modern browsers right back to IE6, can give you rule and property collections like Firebug (in fact it's more accurate than Firebug), and can also calculate the relative or absolute specificity of any rule. The only caveat is that, although it understands static media types, it doesn't understand media-queries.

brothercake
  • 229
  • 2
  • 2
6

Here is my version of getMatchedCSSRules function which support @media query.

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
  • 2,987
  • 1
  • 22
  • 25
4

Here's a version of S.B.'s answer which also returns matching rules within matching media queries. I've removed the *.rules || *.cssRules coalescence and the .matches implementation finder; add a polyfill or add those lines back in if you need them.

This version also returns the CSSStyleRule objects rather than the rule text. I think this is a little more useful, since the specifics of the rules can be more easily probed programmatically this way.

Coffee:

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
  • 9,541
  • 4
  • 55
  • 74
  • How could this be changed to be used on children of the passed `element` as well? – Kragalon Apr 09 '16 at 21:00
  • 2
    What's your use case? I don't really see where that would be useful, since rules which apply to children don't necessarily apply to the parent. You'd just end up with a pile of rules with nothing in particular in common. If you really want that you could just recurse over children and run this method for each, and build up an array of all the results. – tremby Apr 10 '16 at 23:25
  • I'm just trying to make `cloneNode(true)` functionality but with styling deep cloned as well. – Kragalon Apr 11 '16 at 22:48
  • And you are building a new stylesheet? Or just attaching style attributes to the new elements? If just attaching style attributes, why not just recurse over the children and repeatedly run `getMatchedCSSRules`? – tremby Apr 12 '16 at 01:31
  • Yes, I'm trying to attach CSS rules to new elements. I ask because `getMatchedCSSRules` is [deprecated](https://groups.google.com/a/chromium.org/forum/#!msg/blink-dev/fd-QLCiLESQ/_mfowSsRR8oJ) – Kragalon Apr 14 '16 at 08:32
  • Oh nevermind, I didn't remember that your function is `getMatchedCSSRules`, my bad :P – Kragalon Apr 14 '16 at 08:34
  • 1
    this condition: if (window.matchMedia(rule.conditionText).matches) {...} prevented a match in my case since "rule.conditionText" was undefined. Without it, it worked. You can try and test this on https://news.ycombinator.com/. "span.pagetop b" has a media query rule that doesn't match with your function as it is. – ayal gelles May 19 '16 at 17:41
  • Given that that code (the `matchMedia` call) wouldn't be running if the object weren't a `CSSMediaRule`, I'm not sure why `conditionText` wouldn't exist on it. I don't have time to test right now, but do you know the answer? What rule in particular is this, and can you find a reason it doesn't have that property? Is there maybe just an empty media query wrapping the rule in the stylesheet? If that's common it should be easy to add an exception in the code for it. – tremby May 19 '16 at 20:13
  • 1
    Chrome doesn't support the conditionText property on CSSMediaRule instances. – Macil Sep 02 '16 at 00:20
  • 1
    I've just tested and agree. That could explain what @ayalgelles was seeing. There are other problems too, for example Firefox throws a SecurityError when trying to get cssRules of a sheet on a different domain; Chrome returns null (and the script chokes on it) in this case. This'd be easy to work around. Now if only I could remember which project I used this on, I might see how I worked around the former issue... As it is, this is just some example code. – tremby Sep 02 '16 at 03:34
2

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
  • 29
  • 2
  • 4
    Pointless duplicate of an [old version](https://stackoverflow.com/revisions/37958301/5) of my answer. Just polluting the page. Complete and up-to-date version: [here](https://stackoverflow.com/a/37958301/6314667). – 7vujy0f0hy Apr 12 '17 at 22:25
1

Ensuring IE9+, I wrote a function which calculates CSS for requested element and its children, and gives possibility to save it to a new className if needed in snippet below.

/**
  * @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
  • 604
  • 1
  • 9
  • 18
  • 2
    **1.** You can replace the whole `computeStyles` subroutine by just `el => getComputedStyle(el).cssText`. Proof: [fiddle](https://jsfiddle.net/80wpuefe/). **2.** `'.' + element.className` is a faulty construction because it assumes existence of one class name. Valid construction is `element.className.replace(/^| /g, '.')`. **3.** Your function ignores possibility of other CSS selectors than just classes. **4.** Your recursion is arbitrarily limited to one level (children but not grandchildren). **5.** Usage: there is no `getElementByClassName`, only `getElementsByClassName` (returns an array). – 7vujy0f0hy Apr 13 '17 at 03:59
1

I think the answer from S.B. should be the accepted one at this point but it is not exact. It is mentioned a few times that there will be some rules that may be missed. Faced with that, I decided to use document.querySelectorAll instead of element.matches. The only thing is that you would need some kind of unique identification of elements to compare it to the one you are looking for. In most cases I think that is achievable by setting its id to have a unique value. That's how you can identify the matched element being yours. If you can think of a general way to match the result of document.querySelectorAll to the element you are looking for that would essentially be a complete polyfill of getMatchedCSSRules.

I checked the performance for document.querySelectorAll since it probably is slower than element.matches but in most cases it should not be a problem. I see that it takes about 0.001 milliseconds.

I also found CSSUtilities library that advertises that it can do this but I feel its old and has not been updated in a while. Looking at its source code, it makes me think there may be cases that it misses.

cagdas_ucar
  • 135
  • 1
  • 3
  • CSSUtilities is really old, but it does return the rules for pseudo states as well (for example it can return hover rules). I have not found any answer here that addresses the pseudo state yet. – mr1031011 Sep 25 '20 at 15:40
  • 1
    you don't need to set a unique id, since html nodes are passed by reference a simple triple equality check between the initial node reference and nodes returned by `querySelectorAll` should return to you the exact node you're looking for – Ismael Mar 27 '22 at 22:43
1

As the linked question is closed as a duplicate of this, I add an answer here instead.

The unanswered part 2: "Once I found the computed style, I want to know where it comes from"

By looping over the document.styleSheets, and looking at the getComputedStyle() before and after you modify it, you can detect what stylesheet is in use. It's far from optimal, but at least it can detect if the rule you looking at is in use or not.

Here is an exemple:

<html><head>
<title>CSS Test</title>
<style id="style-a">
li {color: #333; font-size: 20px !important;}
li.bb {color: #600; font-size: 10px;}
p {margin: 5px;}
p {margin-bottom: 10px;}
</style>
<script>
window.addEventListener('DOMContentLoaded', async () => {
    const selector = 'li';
    // const selector = 'li.bb';
    const exempleValues = {
        'color': ['rgb(0, 0, 0)', 'rgb(255, 255, 255)'],
        'font-size': ['10px', '12px'],
    };
    const delay = (t) => new Promise((k, e) => {setTimeout(k, t)});

    for(const element of document.querySelectorAll(selector)) {
        const elementCss = document.defaultView.getComputedStyle(element);
        for(const sheet of document.styleSheets) {
            for(const rule of sheet.cssRules) {
                if(rule.selectorText !== selector) {
                    continue;
                }
                for(const properyName of rule.style) {
                    const currentValue = rule.style[properyName];
                    const priority = rule.style.getPropertyPriority(properyName)
                    if(!exempleValues[properyName]) {
                        console.warn('no exemple values for', properyName);
                        continue;
                    }
                    const exempleValue = exempleValues[properyName][exempleValues[properyName][0] === currentValue ? 1 : 0];
                    rule.style.setProperty(properyName, exempleValue, priority);
                    await delay(100);
                    if(exempleValue === elementCss[properyName]) {
                        console.log(selector, properyName, currentValue, priority || false, true, 'in use', element, sheet.ownerNode);
                    } else {
                        console.log(selector, properyName, currentValue, priority || false, false, 'overrided', element);
                    }
                    rule.style.setProperty(properyName, currentValue, priority);
                    await delay(100);
                }
            }
        }
    }
}, {once: true});
</script>
</head><body>
<h1>CSS Test</h1>
<p>html-file for testing css</p>
<ul>
    <li>AAAA</li>
    <li class="bb">BBBB</li>
    <li>CCCC</li>
</ul>
</body></html>
Puggan Se
  • 5,738
  • 2
  • 22
  • 48