【问题标题】:Find all CSS rules that apply to an element查找适用于元素的所有 CSS 规则
【发布时间】:2011-02-26 12:23:30
【问题描述】:

许多工具/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 规则等等)。

【问题讨论】:

标签: javascript css


【解决方案1】:

编辑:此答案现已弃用,no longer works in Chrome 64+。离开历史背景。事实上,错误报告链接回这个问题,以获得使用它的替代解决方案。


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

就这么简单:

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

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

【讨论】:

  • 好吧,如果它只被 chrome 支持的话,这没什么用处。它适用于不到 5% 的访问者(取决于人口统计)。
  • @diamandiev:截至 2012 年 6 月,Chrome 的使用份额已增加到 32% 以上(略高于 IE 的使用率!)。 gs.statcounter.com
  • getMatchedCSSRules 不会显示应用于元素的最终样式。它返回一个包含所有 CSSStyleRule 对象的数组,这些对象按照它们出现的顺序应用。如果您通过 CSS 媒体查询进行响应式 Web 设计或加载多个样式表(例如 IE 的一个),您仍然需要遍历返回的每个样式并计算每个规则的 css 特异性。然后计算适用的最终规则。您需要重现浏览器自然所做的事情。为了在您的示例中证明这一点,请在样式声明的开头添加“p {color: blue !important}”。
  • Chrome 41 现已弃用此功能。请参阅 code.google.com/p/chromium/issues/detail?id=437569#c2
  • 这终于是removed in Chrome 63(官方博文-指向这个问题)
【解决方案2】:

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

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

【讨论】:

  • 这个模块真的很棒,希望能得到作者更多的喜爱。
  • 这个库是否有任何维护版本或替代方案?此刻连库都下载不了……
  • 如何在节点中使用这个库?
【解决方案3】:

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

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

【讨论】:

  • 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
  • 不幸的是,我认为这种替代方法没有检测到从子元素中的父元素级联的所有 CSS 规则。小提琴:jsfiddle.net/t554xo2L 在这种情况下,UL 规则(适用于元素)与if (a.matches(rules[r].selectorText)) 保护条件不匹配。
  • 我从未声称它列出了 /inherited/ CSS 规则——它所做的只是列出与传递的元素匹配的 CSS 规则。如果您还想获取该元素的继承规则,您可能需要向上遍历 DOM 并在每个父元素上调用 css()
  • 我知道 :-) 我只是想指出这一点,因为可以研究这个问题的人可能会认为它得到了“所有适用于元素的 css 规则”作为问题的标题说,事实并非如此。
  • 如果您希望当前应用于元素的所有规则,包括继承的规则,您应该使用 getComputedStyle。鉴于此,我认为这个答案是正确的,并且不包括从父母继承的样式(例如,分配给父母的文本颜色)是正确的。但是,它不包括有条件地应用于媒体查询的规则。
【解决方案4】:

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

【讨论】:

  • 如何将其更改为也用于传递的element 的子代?
  • 您的用例是什么?我真的不知道这在哪里有用,因为适用于孩子的规则不一定适用于父母。你最终会得到一堆没有特别共同点的规则。如果你真的想要,你可以递归孩子并为每个孩子运行这个方法,并建立一个包含所有结果的数组。
  • 我只是在尝试制作 cloneNode(true) 功能,但同时也对样式进行了深度克隆。
  • 这种情况:if (window.matchMedia(rule.conditionText).matches) {...} 在我的情况下阻止了匹配,因为“rule.conditionText”未定义。没有它,它起作用了。您可以尝试在news.ycombinator.com 上进行测试。 “span.pagetop b”的媒体查询规则与您的功能不匹配。
  • Chrome 不支持 CSSMediaRule 实例上的 conditionText 属性。
【解决方案5】:

短版2017 年 4 月 12 日

挑战者出现。

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

/* 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 一起使用。

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 [] } 让它为我工作。
  • 测试了“长版”。为我工作。太糟糕了 getMatchedCSSRules() 从未被浏览器标准化。
  • 如何处理具有相同特性的两个选择器,例如 h1 和 h1、div - 应该使用最后声明的选择器?
  • 也许我们可以在这里得到一些处理伪的想法? github.com/dvtng/jss/blob/master/jss.js
【解决方案6】:

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>

【讨论】:

  • 我的答案的 old version 毫无意义的重复。只是污染页面。完整的最新版本:here.
【解决方案7】:

为了确保 IE9+,我编写了一个函数来计算请求元素及其子元素的 CSS,如果需要,可以在下面的 sn-p 中将其保存到新的 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);');

【讨论】:

  • 1. 您可以将整个 computeStyles 子例程替换为 el =&gt; getComputedStyle(el).cssText。证明:fiddle2. '.' + element.className 是一种错误的构造,因为它假定存在一个类名。有效的构造是element.className.replace(/^| /g, '.')3. 您的函数忽略了其他 CSS 选择器的可能性,而不仅仅是类。 4. 您的递归被任意限制在一个级别(子级而不是孙级)。 5. 用法:没有getElementByClassName,只有getElementsByClassName(返回一个数组)。
【解决方案8】:

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

【讨论】:

    【解决方案9】:

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

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

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

    【讨论】:

    • CSSUtilities 确实很老了,但它也确实返回了伪状态的规则(例如它可以返回悬停规则)。我还没有在这里找到任何解决伪状态的答案。
    【解决方案10】:

    由于the linked question 与此重复,因此我在此处添加了答案。

    未回答的第 2 部分:“一旦我找到了计算样式,我就想知道它来自哪里”

    通过遍历 document.styleSheets,并查看修改前后的 getComputedStyle(),您可以检测出正在使用的样式表。 它远非最佳,但至少它可以检测您查看的规则是否正在使用。

    这是一个例子:

    <html><head>
    <title>CSS Test</title>
    <style id="style-a">
    li {color: #333; font-size: 20px !important;}
    li.bb {color: #600; font-size: 10px;}
    p {margin: 5px;}
    p {margin-bottom: 10px;}
    </style>
    <script>
    window.addEventListener('DOMContentLoaded', async () => {
        const selector = 'li';
        // const selector = 'li.bb';
        const exempleValues = {
            'color': ['rgb(0, 0, 0)', 'rgb(255, 255, 255)'],
            'font-size': ['10px', '12px'],
        };
        const delay = (t) => new Promise((k, e) => {setTimeout(k, t)});
    
        for(const element of document.querySelectorAll(selector)) {
            const elementCss = document.defaultView.getComputedStyle(element);
            for(const sheet of document.styleSheets) {
                for(const rule of sheet.cssRules) {
                    if(rule.selectorText !== selector) {
                        continue;
                    }
                    for(const properyName of rule.style) {
                        const currentValue = rule.style[properyName];
                        const priority = rule.style.getPropertyPriority(properyName)
                        if(!exempleValues[properyName]) {
                            console.warn('no exemple values for', properyName);
                            continue;
                        }
                        const exempleValue = exempleValues[properyName][exempleValues[properyName][0] === currentValue ? 1 : 0];
                        rule.style.setProperty(properyName, exempleValue, priority);
                        await delay(100);
                        if(exempleValue === elementCss[properyName]) {
                            console.log(selector, properyName, currentValue, priority || false, true, 'in use', element, sheet.ownerNode);
                        } else {
                            console.log(selector, properyName, currentValue, priority || false, false, 'overrided', element);
                        }
                        rule.style.setProperty(properyName, currentValue, priority);
                        await delay(100);
                    }
                }
            }
        }
    }, {once: true});
    </script>
    </head><body>
    <h1>CSS Test</h1>
    <p>html-file for testing css</p>
    <ul>
        <li>AAAA</li>
        <li class="bb">BBBB</li>
        <li>CCCC</li>
    </ul>
    </body></html>
    

    【讨论】:

      猜你喜欢
      • 2022-01-22
      • 1970-01-01
      • 1970-01-01
      • 2011-08-20
      • 1970-01-01
      • 2016-09-13
      • 2010-12-10
      • 1970-01-01
      相关资源
      最近更新 更多