首页 文章

查找适用于元素的所有CSS规则

提问于
浏览
70

许多工具/ API提供了选择特定类或ID元素的方法 . 还可以检查浏览器加载的原始样式表 .

但是,对于浏览器呈现元素,它们将编译所有CSS规则(可能来自不同的样式表文件)并将其应用于元素 . 这就是您在Firebug或WebKit Inspector中看到的内容 - 一个元素的完整CSS继承树 .

如何在纯JavaScript中重现此功能而无需其他浏览器插件?

也许一个例子可以为我正在寻找的东西提供一些澄清:

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

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

这里p#描述元素应用了两个CSS规则:红色和20像素的字体大小 .

我想找到这些计算出来的CSS规则源自的来源(颜色来自p规则等) .

8 回答

  • 20

    由于这个问题目前没有轻量级(非库),跨浏览器兼容的答案,我将尝试提供一个:

    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/

    调用 css(document.getElementById('elementId')) 将返回一个数组,其中包含与传递的元素匹配的每个CSS规则的元素 . 如果要查找有关每个规则的更多具体信息,请查看CSSRule object文档 .

  • 15

    编辑:这个答案现已弃用,no longer works in Chrome 64+ . 留下历史背景 . 事实上,错误报告链接回到这个问题,以寻找使用它的替代解决方案 .


    似乎我在经过一个小时的研究后设法回答了我自己的问题 .

    这很简单:

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

    (适用于WebKit / Chrome,也可能适用于其他人)

  • 61

    看看这个库,它可以满足要求:http://www.brothercake.com/site/resources/scripts/cssutilities/

    它适用于所有现代浏览器,可以回到IE6,可以为你提供像Firebug这样的规则和属性集合(实际上它比Firebug更准确),并且还可以计算任何规则的相对或绝对特异性 . 唯一需要注意的是,尽管它理解静态媒体类型,但它并不了解媒体查询 .

  • 0

    短版本2017年4月12日

    挑战者出现了 .

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

    Line /* 1 */ 构建了一个包含所有规则的平面数组 .
    /* 2 */ 丢弃不匹配的规则 .

    基于@ S.B的function css(el) . 在同一页上 .

    示例1

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

    示例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>
    

    缺点

    • 无媒体处理,没有 @import@media .

    • 无法访问从跨域样式表加载的样式 .

    • 没有按选择器“特异性”排序(重要性顺序) .

    • 没有从父项继承的样式 .

    • 可能无法与旧的或基本的浏览器一起使用 .

    • 不确定它如何应对伪类和伪选择器,但似乎没有问题 .

    也许有一天我会解决这些缺点 .

    长版本2018年8月12日

    这是一个更全面的实现,取自someone’s GitHub page(从original code分叉,通过Bugzilla) . 写给Gecko和IE,但据传也与Blink合作 .

    4 May 2017: 特异性计算器已经遇到了我现在修复过的关键错误 . (我无法通知作者,因为我没有GitHub帐户 . )

    12 August 2018: 最近的Chrome更新似乎已将对象范围( this )与分配给自变量的方法分离 . 因此调用 matcher(selector) 已停止工作 . 用 matcher.call(el, selector) 替换它解决了它 .

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

    修正了错误

    • = match+= match

    • return re ? re.length : 0;return matches ? matches.length : 0;

    • _matchesSelector(element, selector)matchesSelector(element, selector)

    • matcher(selector)matcher.call(el, selector)

  • 3

    这里's a version of S.B.'的答案也返回匹配媒体查询中的匹配规则 . 我删除了 *.rules || *.cssRules 合并和 .matches 实现查找器;如果需要,可以添加一个polyfill或添加这些行 .

    此版本还返回 CSSStyleRule 对象而不是规则文本 . 我认为这更有用,因为规则的细节可以通过这种方式更容易地以编程方式进行探测 .

    咖啡:

    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;
    }
    
  • 15
    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>
    
  • 1

    确保IE9,我编写了一个函数来为所请求的元素及其子元素计算CSS,并且如果需要在下面的代码片段中提供将其保存到新的className的可能性 .

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

    这是我的 getMatchedCSSRules 函数版本,它支持 @media 查询 .

    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
    }
    

相关问题