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


87

许多工具/ 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 px的字体大小。

我想找到这些计算CSS规则的来源(颜色来自p规则,依此类推)。



在浏览器和用户中查看浏览器开发人员工具(例如Chrome中的“元素”标签)?
罗尼·罗伊斯顿

Answers:


77

由于此问题当前尚无轻量级(非库),跨浏览器兼容的答案,因此,我将尝试提供一个答案:

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


1
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
SB

3
不幸的是,我认为这种替代方法无法检测到所有从子级父级元素级联的CSS规则。小提琴:jsfiddle.net/t554xo2L在这种情况下,UL规则(适用于该元素)未与if (a.matches(rules[r].selectorText))保护条件匹配。
funforums 2015年

2
我从未声称它列出了/ inherited / CSS规则-它所做的只是列出与传递的元素匹配的CSS规则。如果还想获取该元素的继承规则,则可能需要向上遍历DOM并调用css()每个父元素。
SB 2015年

2
我知道:-)我只是想指出这一点,因为可以调查这个问题的人可能会假设它获得了“适用于某个元素的所有CSS规则”,正如问题的标题所说,事实并非如此。 。
funforums

3
如果要当前将所有规则(包括继承的规则)应用于元素,则应使用getComputedStyle。有鉴于此,我认为这个答案是正确的,并且不包含从父母那里继承的样式(例如,分配给父母的文本颜色)是正确的。但是,它不包括的是有条件地应用于媒体查询的规则。
tremby

23

编辑:现在不赞成使用此答案,并且不再适用于Chrome 64+。留给历史背景。实际上,错误报告链接回此问题,以提供使用此问题的替代解决方案。


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

就这么简单:

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

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


4
好吧,如果仅由chrome支持,则没有太多用处。它将无法满足所有访问者的5%(取决于受众特征)。
Tomasi 2010年

5
@diamandiev:截至2012年6月,Chrome使用率份额已增加到32%以上(并且略高于IE使用率!)。gs.statcounter.com
罗伊·廷克

6
getMatchedCSSRules不会向您显示适用于该元素的最终样式。它返回一个按其出现顺序应用的所有CSSStyleRule对象的数组。如果通过CSS媒体查询进行响应式Web设计或加载多个样式表(如IE中的样式表),则仍然需要遍历返回的每种样式并为每条规则计算CSS特异性。然后计算适用的最终规则。您需要重现浏览器的自然功能。为了在您的示例中证明这一点,请在样式声明的开头添加“ p {color:blue!important}”。
mrbinky3000

24
Chrome 41现在已弃用此功能。请参见code.google.com/p/chromium/issues/detail?id=437569#c2
Daniel Darabos

5
这终于在Chrome 63中删除(官方博客文章-指向这个问题)
brichins 17-10-20

19

看一下该库,它可以满足您的要求:http : //www.brothercake.com/site/resources/scripts/cssutilities/

它可以在IE6以后的所有现代浏览器中运行,可以为您提供规则和属性集合,例如Firebug(实际上比Firebug更准确),还可以计算任何规则的相对或绝对特异性。唯一需要注意的是,尽管它了解静态媒体类型,但不了解媒体查询。


这个模块真的很棒,只是希望它能得到作者的更多喜爱。
mr1031011

17

短版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)

在getSheetRules中,我必须添加if(stylesheet.cssRules === null){return []}以使其对我有用。
Gwater17

测试了“长版”。为我工作。糟糕的是,getMatchedCSSRules()从未被浏览器标准化。
科林·莫克

如何处理两个具有相同特性的选择器,如h1和h1,div-应该使用最后声明的选择器?
斯特拉

也许我们可以在这里得到一些处理伪的想法?github.com/dvtng/jss/blob/master/jss.js
mr1031011

4

这是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;
}

如何将其更改为也可以用于通过者的子代element
Kragalon '16

2
您的用例是什么?我真的看不出在哪里有用,因为适用于孩子的规则不一定适用于父母。您最终会得到一堆没有特别共同之处的规则。如果您确实希望,可以对子项进行递归并为每个子项运行此方法,并建立所有结果的数组。
tremby '16

我只是在尝试进行cloneNode(true)功能设计,同时也设计了深克隆样式。
Kragalon '16

1
这种情况:if(window.matchMedia(rule.conditionText).matches){...}在我的案例中阻止了匹配,因为未定义“ rule.conditionText”。没有它,它会起作用。您可以尝试在news.ycombinator.com上进行测试。“ span.pagetop b”的媒体查询规则与您的功能不匹配。
ayal gelles's

1
Chrome浏览器不支持CSSMediaRule实例上的conditionText属性。
Macil 2016年

3

这是我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
}

1

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>


3
我的答案的旧版本毫无意义的重复。只是污染页面。完整和最新版本:此处
7vujy0f0hy

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

用法

getElementStyles(document.getElementByClassName('.my-class'), '.dummy-class', 'width:100%;opaity:0.5;transform:scale(1.5);');

2
1.您可以用替换整个computeStyles子例程el => getComputedStyle(el).cssText。证明:小提琴2. '.' + element.className构造错误,因为它假定存在一个类名。有效构造为element.className.replace(/^| /g, '.')3.您的函数会忽略其他CSS选择器的可能性,而不仅仅是类。4.您的递归被任意地限制为一个级别(子级而不是子级)。5.用法:没有getElementByClassName,只有getElementsByClassName(返回一个数组)。
7vujy0f0hy

1

我认为,此时SB的答案应该是公认的答案,但这还不确切。几次提到可能会遗漏一些规则。面对这一点,我决定使用document.querySelectorAll代替element.matches。唯一的是,您需要某种独特的元素标识才能将其与您要查找的元素进行比较。在大多数情况下,我认为可以通过将其ID设置为具有唯一值来实现。这样便可以确定匹配的元素是您的。如果您可以想到一种将document.querySelectorAll的结果与您要查找的元素进行匹配的一般方法,则该方法实际上就是getMatchedCSSRules的完整polyfill。

我检查了document.querySelectorAll的性能,因为它可能比element.matches慢,但在大多数情况下应该不成问题。我看到它大约需要0.001毫秒。

我还找到了CSSUtilities库,该库宣传它可以做到这一点,但是我觉得它很旧,并且已经有一段时间没有更新了。查看其源代码,它使我认为可能遗漏了某些情况。


CSSUtilities确实很老,但是它也返回伪状态的规则(例如,它可以返回悬停规则)。我在这里还没有找到解决伪状态的答案。
mr1031011
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.