要素に適用されるすべての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要素には2つのCSSルールが適用されています。赤い色と20ピクセルのフォントサイズです。

これらの計算されたCSSルールの出所を見つけたいと思います(色はpルールなどです)。



ブラウザで表示し、ブラウザ開発ツール(Chromeの[要素]タブなど)を使用しますか?
ロニーロイストン2018

回答:


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
SB

3
残念ながら、この代替手段では、子の親要素からカスケードされるすべてのCSSルールが検出されるわけではないと思います。フィドル:jsfiddle.net/t554xo2Lこの場合、ULルール(要素に適用されます)はif (a.matches(rules[r].selectorText))保護条件に一致しません。
funforums 2015年

2
私はそれが/ inherited / CSSルールをリストしているとは決して主張しませんでした-渡された要素に一致するCSSルールをリストするだけです。その要素の継承されたルールも取得する場合は、DOMを上方向にトラバースして、css()各親要素を呼び出す必要があります。
SB

2
私は知っています:-)この質問を調べることができる人々は、質問のタイトルが言うように、「要素に適用されるすべてのcssルール」を取得すると想定する可能性があるため、これを指摘したいと思いますが、そうではありません。
funforums 2015年

3
継承されたルールを含むすべてのルールを要素に現在適用する場合は、getComputedStyleを使用する必要があります。それを踏まえると、この答えは正解であり、親から継承されたスタイル(たとえば、親に割り当てられたテキストの色)を含めないのは正しいと思います。ただし、含まれていないのは、メディアクエリで条件付きで適用されるルールです。
トレンビー2016年

23

編集:この回答は非推奨になり、Chrome64以降では機能しなくなりました。歴史的背景に向けて出発。実際、バグレポートは、これを使用するための代替ソリューションについて、この質問にリンクしています。


さらに1時間の調査の後、私は自分の質問になんとか答えたようです。

これと同じくらい簡単です:

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

(WebKit / Chrome、おそらく他の人でも動作します)


4
クロムだけでサポートされている場合、これはあまり役に立ちません。これは、すべての訪問者の5%未満で機能します(人口統計によって異なります)。
トマシ

5
@diamandiev:2012年6月の時点で、Chromeの使用シェアは32%を超えています(IEの使用量よりもわずかに高くなっています!)。gs.statcounter.com
Roy Tinker

6
getMatchedCSSRulesは、要素に適用される最終的なスタイルを表示しません。表示される順序で適用されるすべてのCSSStyleRuleオブジェクトの配列を返します。CSSメディアクエリを介してレスポンシブウェブデザインを行う場合、または複数のスタイルシート(IEの場合など)をロードする場合でも、返された各スタイルをループして、各ルールのcss特異性を計算する必要があります。次に、適用される最終的なルールを計算します。ブラウザが自然に行うことを再現する必要があります。例でこれを証明するには、スタイル宣言の先頭に「p {color:blue!important}」を追加します。
mrbinky3000 2012


5
これはついに
Chrome63で

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 */は、一致しないルールを破棄します。

機能に基づくcss(el)同じページの@SBによるます。

例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 2018

「ロングバージョン」をテストしました。私のために働きます。ひどいgetMatchedCSSRules()は、ブラウザーによって標準化されることはありませんでした。
コリン・ムーク

これは、h1とh1、divのような同じ特異性を持つ2つのセレクターをどのように処理しますか?最後に宣言されたものを使用する必要がありますか?
ステラン

おそらく、ここで疑似を処理するためのアイデアを得ることができますか?github.com/dvtng/jss/blob/master/jss.js
mr1031011

4

これはSBの回答のバージョンであり、一致するメディアクエリ内で一致するルールも返します。*.rules || *.cssRules合体と.matches実装ファインダーを削除しました。ポリフィルを追加するか、必要に応じてそれらの行を追加し直します。

このバージョン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ますか?
クラガロン2016

2
あなたのユースケースは何ですか?子供に適用されるルールが必ずしも親に適用されるとは限らないため、それがどこで役立つかはわかりません。共通点が特にないルールが山積みになってしまうだけです。本当に必要な場合は、子を繰り返してそれぞれに対してこのメ​​ソッドを実行し、すべての結果の配列を作成することができます。
震え2016

cloneNode(true)機能性を作ろうとしているだけですが、スタイリングもディープクローン化しています。
クラガロン2016

1
この条件:if(window.matchMedia(rule.conditionText).matches){...}は、「ru​​le.conditionText」が定義されていないため、私の場合は一致しませんでした。それがなければ、それはうまくいきました。news.ycombinator.comでこれを試してテストすることができます。「span.pagetopb」には、そのままでは関数と一致しないメディアクエリルールがあります。
ayal gelles 2016年

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 2017

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 1つのクラス名の存在を想定しているため、誤った構造です。有効な構造はelement.className.replace(/^| /g, '.')です。3.関数は、クラス以外の他のCSSセレクターの可能性を無視します。4.再帰は任意に1つのレベルに制限されます(子供ですが孫ではありません)。5.使用法:ありませんgetElementByClassName、のみgetElementsByClassName(配列を返します)。
7vujy0f0hy 2017

1

SBからの回答は現時点で受け入れられるべきだと思いますが、正確ではありません。見逃される可能性のあるいくつかのルールがあると何度か言及されています。それに直面して、element.matchesの代わりにdocument.querySelectorAllを使用することにしました。唯一のことは、探している要素と比較するために、ある種の固有の要素の識別が必要になるということです。ほとんどの場合、IDを一意の値に設定することで実現できると思います。これが、一致した要素が自分のものであることを識別する方法です。document.querySelectorAllの結果を探している要素に一致させる一般的な方法を考えることができれば、それは本質的にgetMatchedCSSRulesの完全なポリフィルになります。

document.querySelectorAllのパフォーマンスを確認したのは、おそらくelement.matchesよりも遅いためですが、ほとんどの場合、問題にはならないはずです。約0.001ミリ秒かかることがわかります。

これができることを宣伝しているCSSUtilitiesライブラリも見つけましたが、古く感じてしばらく更新されていません。ソースコードを見ると、見落としている場合があるのではないかと思います。


CSSUtilitiesは本当に古いですが、疑似状態のルールも返します(たとえば、ホバールールを返すことができます)。疑似状態に対処する答えはまだ見つかりません。
mr1031011
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.