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

IT技术 javascript css
2021-01-13 05:27:16

许多工具/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#description 元素应用了两个 CSS 规则:红色和 20 像素的字体大小。

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

6个回答

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

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 对象文档。

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

编辑:此答案现已弃用,不再适用于 Chrome 64+离开历史背景。事实上,该错误报告链接回此问题,以获取使用此问题的替代解决方案。


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

就这么简单:

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

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

@diamandiev:截至 2012 年 6 月,Chrome 使用份额已增加至 32% 以上(略高于 IE 使用率!)。gs.statcounter.com
2021-03-18 05:27:16
getMatchedCSSRules 不会向您显示适用于该元素的最终样式。它返回一个包含所有 CSSStyleRule 对象的数组,这些对象按照它们出现的顺序应用。如果您通过 CSS 媒体查询进行响应式网页设计或加载多个样式表(例如 IE 的样式表),您仍然需要遍历返回的每个样式并计算每个规则的 css 特异性。然后计算适用的最终规则。您需要重现浏览器自然执行的操作。为了在您的示例中证明这一点,请在样式声明的开头添加“p {color: blue !important}”。
2021-03-21 05:27:16
好吧,如果它仅由 chrome 支持,这并没有多大用处。它将适用于不到所有访问者的 5%(取决于人口统计数据)。
2021-03-23 05:27:16
这终于在 Chrome 63 中删除了(官方博客文章 - 指向这个问题)
2021-03-24 05:27:16
这在 Chrome 41 中现已弃用。请参阅code.google.com/p/chromium/issues/detail?id=437569#c2
2021-04-10 05:27:16

简短版本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 */丢弃不匹配的规则。

基于同一页面@SB 的功能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 日

这是取自某人的 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);
        };
}

修正错误

  • = match+= match
  • return re ? re.length : 0;return matches ? matches.length : 0;
  • _matchesSelector(element, selector)matchesSelector(element, selector)
  • matcher(selector)matcher.call(el, selector)
这如何处理两个具有相同特性的选择器,例如 h1 和 h1,div - 应该使用最后声明的选择器?
2021-03-12 05:27:16
也许我们可以在这里得到一些处理伪的想法?github.com/dvtng/jss/blob/master/jss.js
2021-03-13 05:27:16
测试了“长版”。为我工作。太糟糕了 getMatchedCSSRules() 从未被浏览器标准化。
2021-03-23 05:27:16
在 getSheetRules 我不得不添加 if(stylesheet.cssRules === null) { return [] } 让它为我工作。
2021-04-06 05:27:16

看看这个库,它做了什么要求:http : //www.brothercake.com/site/resources/scripts/cssutilities/

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

如何在节点中使用这个库?
2021-03-16 05:27:16
这个module真的很棒,希望能得到作者更多的喜爱。
2021-03-29 05:27:16
该库是否有任何维护版本或替代方案?此时甚至无法下载库...
2021-04-07 05:27:16

这是 SB 答案的一个版本,它也返回匹配媒体查询中的匹配规则。我已经删除了*.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;
}
此条件: if (window.matchMedia(rule.conditionText).matches) {...} 在我的情况下阻止了匹配,因为“rule.conditionText”未定义。没有它,它起作用了。您可以在news.ycombinator.com上尝试测试“span.pagetop b”有一个与您的功能不匹配的媒体查询规则。
2021-03-13 05:27:16
Chrome 不支持 CSSMediaRule 实例上的 conditionText 属性。
2021-03-25 05:27:16
如何将其更改为也用于已故的孩子element
2021-04-08 05:27:16
你的用例是什么?我真的不知道这有什么用处,因为适用于孩子的规则不一定适用于父母。你最终只会得到一堆没有什么特别共同点的规则。如果你真的想要,你可以递归地遍历子节点并为每个子节点运行这个方法,然后构建一个包含所有结果的数组。
2021-04-09 05:27:16
我只是在尝试制作cloneNode(true)功能,但也要深度克隆样式。
2021-04-10 05:27:16