Find all CSS rules that apply to an element

后端 未结 9 823
温柔的废话
温柔的废话 2020-11-22 10:26

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,

相关标签:
9条回答
  • 2020-11-22 10:57

    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.

    0 讨论(0)
  • 2020-11-22 10:57

    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.

    0 讨论(0)
  • 2020-11-22 10:58

    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)
    0 讨论(0)
  • 2020-11-22 10:58

    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;
    }
    
    0 讨论(0)
  • 2020-11-22 11:05

    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>

    0 讨论(0)
  • 2020-11-22 11:06

    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);');
    
    0 讨论(0)
提交回复
热议问题