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

cgb*_*rom 78 javascript css

许多工具/ 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>
Run Code Online (Sandbox Code Playgroud)

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

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

S.B*_*.B. 66

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

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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • 我知道:-)我只是想指出这一点,因为可以调查这个问题的人可能会假设它得到了“适用于一个元素的所有 css 规则”,正如问题的标题所说,事实并非如此. (3认同)
  • 如果您希望当前应用于元素的所有规则包括继承的规则,您应该使用 getComputedStyle。有鉴于此,我认为这个答案是正确的,并且不包括从父母继承的样式(例如分配给父母的文本颜色)是正确的。但是,它不包括有条件地应用于媒体查询的规则。 (3认同)
  • `a.matches` 在这一行中定义:`a.matches = a.matches || a.webkitMatchesSelector || a.mozMatchesSelector || a.msMatchesSelector || a.oMatchesSelector`。这意味着,如果 DOM 节点已经有一种(标准)“匹配”方法,它将使用该方法,否则它会尝试使用 Webkit 特定的方法(webkitMatchesSelector),然后是 Mozilla、Microsoft 和 Opera 的方法。您可以在这里阅读更多相关信息:https://developer.mozilla.org/en/docs/Web/API/Element/matches (2认同)
  • 不幸的是,我认为这个替代方案不会检测从子元素中的父元素级联的所有CSS规则.小提琴:http://jsfiddle.net/t554xo2L/在这种情况下,UL规则(适用于元素)不匹配于`if(a.matches(rules [r] .selectorText))`保护条件. (2认同)
  • 我从未声称它列出了 /inherited/ CSS 规则 - 它所做的只是列出与传递的元素匹配的 CSS 规则。如果您还想获得该元素的继承规则,您可能需要向上遍历 DOM 并在每个父元素上调用 `css()`。 (2认同)

cgb*_*rom 22

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


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

这很简单:

window.getMatchedCSSRules(document.getElementById("description"))
Run Code Online (Sandbox Code Playgroud)

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

  • 现在,Chrome 41已弃用此功能.请参阅https://code.google.com/p/chromium/issues/detail?id=437569#c2. (23认同)
  • getMatchedCSSRules不会显示适用于该元素的最终样式.它返回一个所有CSSStyleRule对象的数组,这些对象按照它们出现的顺序应用.如果您通过CSS媒体查询进行响应式网页设计或加载多个样式表(如IE的一个样式表),您仍需要遍历返回的每个样式并计算每个规则的css特异性.然后计算适用的最终规则.您需要重现浏览器自然执行的操作.要在您的示例中证明这一点,请在样式声明的开头添加"p {color:blue!important}". (6认同)
  • @diamandiev:截至2012年6月,Chrome使用率已增加到超过32%(略高于IE使用率!).http://gs.statcounter.com/ (5认同)
  • 如果只有chrome支持它,这没有多大用处.它将适用于所有访问者的不到5%(取决于人口统计). (4认同)
  • 这最终[已在Chrome 63中移除](https://developers.google.com/web/updates/2017/10/chrome-63-deprecations)(官方博客文章 - 其中指回此问题) (4认同)
  • 弃用被推回 [Chrome 64](​​https://bugs.chromium.org/p/chromium/issues/detail?id=437569&amp;desc=2)。现在终于没有了。 (2认同)

小智 17

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

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

  • 该库是否有任何维护版本或替代版本?目前该库甚至无法下载...... (3认同)
  • 这个模块真的很棒,希望得到作者更多的喜爱。 (2认同)

7vu*_*0hy 15

简短版本2017年4月12日

挑战者出现了.

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

Line /* 1 */构建所有规则的平面数组.
线/* 2 */丢弃不匹配的规则.

基于@SB在同一页面上的功能css(el).

例1

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

例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"));
Run Code Online (Sandbox Code Playgroud)
.red {color: red;}
#big {font-size: 20px;}
Run Code Online (Sandbox Code Playgroud)
<h3 id="big" class="red" style="margin: 0">Lorem ipsum</h3>
<textarea id="show" cols="70" rows="10"></textarea>
Run Code Online (Sandbox Code Playgroud)

缺点

  • 没有媒体处理,不@import,@media.
  • 无权访问从跨域样式表加载的样式.
  • 没有按选择器"特异性"排序(重要性顺序).
  • 没有从父母继承的风格.
  • 可能无法使用旧的或基本的浏览器.
  • 不知道它如何处理伪类和伪选择器,但似乎没有问题.

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

长版本2018年8月12日

这是一个更全面的实现,取自某人的GitHub页面 (从这个原始代码分叉,通过Bugzilla).写给Gecko和IE,但据传也与Blink合作.

2017年5月4日:特异性计算器已经出现了重要的错误,我现在已经修复了.(我无法通知作者,因为我没有GitHub帐户.)

2018年8月12日:最近的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);
        };
}
Run Code Online (Sandbox Code Playgroud)

修正了错误

  • = match+= match
  • return re ? re.length : 0;return matches ? matches.length : 0;
  • _matchesSelector(element, selector)matchesSelector(element, selector)
  • matcher(selector)matcher.call(el, selector)


use*_*501 6

这是我的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
}
Run Code Online (Sandbox Code Playgroud)