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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2952667/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 02:38:53  来源:igfitidea点击:

Find all CSS rules that apply to an element

javascriptcss

提问by cgbystrom

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.

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

However, for browsers to render an element, they'll compile all CSS rules (possibly from different stylesheet files) and apply it to the element. This is what you see with Firebug or the WebKit Inspector - the full CSS inheritance tree for an element.

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

How can I reproduce this feature in pure JavaScript without requiring additional browser plugins?

如何在不需要额外浏览器插件的情况下用纯 JavaScript 重现此功能?

Perhaps an example can provide some clarification for what I'm looking for:

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

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

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

Here the p#description element have two CSS rules applied: a red color and a font size of 20 px.

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

I would like to find the source from where these computed CSS rules originate from (color comes the p rule and so on).

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

采纳答案by cgbystrom

EDIT: This answer is now deprecated and no longer works in Chrome 64+. Leaving for historical context. In fact that bug report links back to this question for alternative solutions to using this.

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



Seems I managed to answer my own question after another hour of research.

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

It's as simple as this:

就这么简单:

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

(Works in WebKit/Chrome, possibly others too)

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

回答by S.B.

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/

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 objectdocumentation.

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

回答by brothercake

Have a look at this library, which does what was asked for: http://www.brothercake.com/site/resources/scripts/cssutilities/

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

It works in all modern browsers right back to IE6, can give you rule and property collections like Firebug (in fact it's more accurate than Firebug), and can also calculate the relative or absolute specificity of any rule. The only caveat is that, although it understands static media types, it doesn't understand media-queries.

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

回答by 7vujy0f0hy

Short version12 April 2017

简短版本2017 年 4 月 12 日

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.

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

Based on function css(el)by @S.B. on the same page.

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

Example 1

示例 1

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

Example 2

示例 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.
  • 没有媒体处理,没有@import@media
  • 无法访问从跨域样式表加载的样式。
  • 没有按选择器“特异性”(重要性顺序)排序。
  • 没有从父母那里继承的风格。
  • 可能不适用于旧的或基本的浏览器。
  • 不确定它如何处理伪类和伪选择器,但似乎还不错。

Maybe I will address these shortcomings one day.

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

Long version12 August 2018

长版2018 年 8 月 12 日

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.

这是取自某人的 GitHub 页面的更全面的实现 (从这个原始代码分叉,通过Bugzilla)。为 Gecko 和 IE 编写,但据传也适用于 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.)

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

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.

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

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)
  • = match+= match
  • return re ? re.length : 0;return matches ? matches.length : 0;
  • _matchesSelector(element, selector)matchesSelector(element, selector)
  • matcher(selector)matcher.call(el, selector)

回答by tremby

Here's a version of S.B.'s answer which also returns matching rules within matching media queries. I've removed the *.rules || *.cssRulescoalescence and the .matchesimplementation finder; add a polyfill or add those lines back in if you need them.

这是 SB 答案的一个版本,它也返回匹配媒体查询中的匹配规则。我已经删除了*.rules || *.cssRules合并和.matches实现查找器;如果需要,添加一个 polyfill 或重新添加这些行。

This version also returns the CSSStyleRuleobjects 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.

此版本还返回CSSStyleRule对象而不是规则文本。我认为这更有用一点,因为通过这种方式可以更轻松地以编程方式探测规则的细节。

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:

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

回答by user3896501

Here is my version of getMatchedCSSRulesfunction which support @mediaquery.

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

回答by cagdas_ucar

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.

我认为 SB 的答案在这一点上应该是公认的,但并不准确。提过几次,可能会漏掉一些规则。面对这种情况,我决定使用 document.querySelectorAll 而不是 element.matches。唯一的问题是您需要某种独特的元素标识来将其与您正在寻找的元素进行比较。在大多数情况下,我认为可以通过将其 id 设置为具有唯一值来实现。这就是您如何识别匹配元素是您的。如果您能想到将 document.querySelectorAll 的结果与您要查找的元素相匹配的一般方法,那么它本质上就是 getMatchedCSSRules 的完整 polyfill。

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.

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

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.

我还发现 CSSUtilities 库宣传它可以做到这一点,但我觉得它很旧并且有一段时间没有更新。看看它的源代码,让我觉得可能有它遗漏的情况。

回答by Thomas

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>

回答by Shobhit Sharma

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.

确保 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);');