Bir öğeye uygulanan tüm CSS kurallarını bulun


87

Birçok araç / API, belirli sınıfların veya kimliklerin öğelerini seçmenin yollarını sağlar. Tarayıcı tarafından yüklenen ham stil sayfalarını incelemek de mümkündür.

Bununla birlikte, tarayıcıların bir öğeyi oluşturması için, tüm CSS kurallarını (muhtemelen farklı stil sayfası dosyalarından) derler ve öğeye uygularlar. Firebug veya WebKit Inspector ile gördüğünüz şey budur - bir öğe için tam CSS miras ağacı.

Ek tarayıcı eklentileri gerektirmeden bu özelliği saf JavaScript'te nasıl yeniden oluşturabilirim?

Belki bir örnek aradığım şey için biraz açıklama sağlayabilir:

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

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

Burada p # açıklama öğesine iki CSS kuralı uygulanır: kırmızı renk ve 20 piksel yazı tipi boyutu.

Bu hesaplanmış CSS kurallarının nereden geldiği kaynağı bulmak istiyorum (renk p kuralıdır vb.).



Bir tarayıcıda görüntüleyin ve tarayıcı geliştirici araçlarını kullanın (ör. Chrome'da Elements sekmesi)?
Ronnie Royston

Yanıtlar:


77

Bu sorunun şu anda hafif (kitaplık dışı), tarayıcılar arası uyumlu bir cevabı olmadığından, bir tane sağlamaya çalışacağım:

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/

Çağırma css(document.getElementById('elementId')), geçirilen öğeyle eşleşen her CSS kuralı için bir öğe içeren bir dizi döndürür. Her bir kural hakkında daha spesifik bilgi edinmek istiyorsanız, CSSRule nesne belgelerine bakın.


1
a.matchesBu satır ile tanımlanmıştır: a.matches = a.matches || a.webkitMatchesSelector || a.mozMatchesSelector || a.msMatchesSelector || a.oMatchesSelector. Bu, DOM Düğümleri için zaten bir (standart) "eşleşme" yöntemi varsa, bunu kullanacağı anlamına gelir, aksi takdirde Webkit'e özgü olanı (webkitMatchesSelector), ardından Mozilla, Microsoft ve Opera'yı kullanmaya çalışır. Bu konuda daha fazla bilgiyi buradan okuyabilirsiniz: developer.mozilla.org/en/docs/Web/API/Element/matches
SB

3
Maalesef, bu alternatifin çocuklarda üst öğelerden gelen tüm CSS kurallarını algılamadığını düşünüyorum. Fiddle: jsfiddle.net/t554xo2L Bu durumda, UL kuralı (eleman için geçerli olan) if (a.matches(rules[r].selectorText))koruma koşuluyla eşleşmez .
funforums

2
Hiçbir zaman listelendiğini / miras aldığını / CSS kurallarını iddia etmedim - tek yaptığı, geçirilen öğeyle eşleşen CSS kurallarını listelemektir. Bu öğe için devralınan kuralları da almak istiyorsanız, muhtemelen DOM'u yukarı doğru hareket ettirmeniz ve css()ana öğelerin her birini çağırmanız gerekir .
SB

2
Biliyorum :-) Bunu belirtmek istedim, çünkü bu soruyu inceleyebilen insanlar, sorunun başlığından da anlaşılacağı gibi 'bir öğeye uygulanan tüm css kurallarını' aldığını varsayabilir, ki durum böyle değil .
funforums

3
Devralınanlar dahil tüm kuralların öğeye uygulanmasını istiyorsanız, getComputedStyle kullanmanız gerekir. Bunun ışığında, bu cevabın doğru olduğunu ve ebeveynlerden miras alınan stilleri (örneğin ebeveyne atanan metin rengi) dahil etmemenin doğru olduğunu düşünüyorum. Bununla birlikte, içermeyen şeyler, medya sorgularıyla koşullu olarak uygulanan kurallardır.
tremby

24

DÜZENLEME: Bu yanıt artık kullanımdan kaldırılmıştır ve artık Chrome 64+ ile çalışmamaktadır . Tarihsel bağlam için ayrılıyor. Aslında bu hata raporu, bunu kullanmak için alternatif çözümler için bu soruya geri dönüyor.


Görünüşe göre bir saatlik araştırmadan sonra kendi sorumu cevaplamayı başardım.

Bu kadar basit:

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

(WebKit / Chrome'da, muhtemelen başkalarında da çalışır)


4
Bu, yalnızca krom tarafından destekleniyorsa pek bir işe yaramaz. Tüm ziyaretçilerin% 5'inden azı için çalışacaktır (demografiye bağlı olarak).
Tomasi

5
@diamandiev: Haziran 2012 itibarıyla, Chrome kullanım payı% 32'nin üzerine çıktı (ve IE kullanımından biraz daha yüksektir!). gs.statcounter.com
Roy Tinker

6
getMatchedCSSRules size öğeye uygulanan son stilleri GÖSTERMEZ. Göründükleri sırada geçerli olan tüm CSSStyleRule nesnelerinin bir dizisini döndürür. CSS ortam sorguları aracılığıyla duyarlı web tasarımı yapıyorsanız veya birden fazla stil sayfası yüklerseniz (IE için bir tane gibi), yine de döndürülen stillerin her birinde döngü yapmanız ve her kural için css özgüllüğünü hesaplamanız gerekir. Ardından geçerli olan son kuralları hesaplayın. Tarayıcının doğal olarak yaptığı şeyi yeniden üretmeniz gerekir. Bunu örneğinizde kanıtlamak için, stil bildiriminizin başına "p {color: blue! İmportant}" ekleyin.
mrbinky3000

24
Bu, artık Chrome 41'de kullanımdan kaldırılmıştır. Code.google.com/p/chromium/issues/detail?id=437569#c2 adresine bakın .
Daniel Darabos

5
Bu nihayet Chrome 63'te kaldırıldı (resmi blog yayını - bu soruya geri dönüyor)
brichins

20

Kısa versiyon 12 Nisan 2017

Challenger belirir.

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

Çizgi /* 1 */, tüm kuralların düz bir dizisini oluşturur.
Satır, /* 2 */eşleşmeyen kuralları iptal eder.

Dayanarak işlevicss(el) aynı sayfada @SB tarafından.

örnek 1

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

Örnek 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>

Eksiklikler

  • Hiçbir medya işleme, hayır @import, @media.
  • Etki alanları arası stil sayfalarından yüklenen stillere erişim yok.
  • Seçici "özgüllük" e göre sıralama yok (önem sırası).
  • Ebeveynlerden miras kalan stil yok.
  • Eski veya ilkel tarayıcılarla çalışmayabilir.
  • Sözde sınıflarla ve sözde seçicilerle nasıl başa çıktığından emin değilim, ancak iyi görünüyor.

Belki bir gün bu eksiklikleri gideririm.

Uzun versiyon 12 Ağustos 2018

İşte birinin GitHub sayfasından alınan çok daha kapsamlı bir uygulama ( Bugzilla aracılığıyla bu orijinal koddan çatallanmıştır ). Gecko ve IE için yazılmış, ancak Blink ile de çalıştığı söyleniyor.

4 Mayıs 2017: Özgüllük hesaplayıcısında şimdi düzelttiğim kritik hatalar var. (Yazarlara bilgi veremiyorum çünkü GitHub hesabım yok.)

12 Ağustos 2018: Son Chrome güncellemeleri, nesne kapsamını ( this) bağımsız değişkenlere atanan yöntemlerden ayırmış görünüyor . Bu nedenle çağrı matcher(selector)çalışmayı durdurdu. Değiştirmek matcher.call(el, selector)sorunu çözdü.

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

Sabit hatalar

  • = 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'e, benim için çalışmasını sağlamak için if (stylesheet.cssRules === null) {return []} eklemek zorunda kaldım.
Gwater17

"Uzun versiyon" u test etti. Benim için çalışıyor. GetMatchedCSSRules () tarayıcılar tarafından hiçbir zaman standartlaştırılmamış olması çok kötü.
colin moock

Bu, h1 ve h1, div gibi aynı özgüllüklere sahip iki seçiciyi nasıl ele alır - burada en son bildirilenin kullanılması gerekir?
Stellan

PEBelki burada sözde işlemek için bir fikir edinebiliriz? github.com/dvtng/jss/blob/master/jss.js
mr1031011

19

İstendiğini yapan bu kitaplığa bir göz atın: http://www.brothercake.com/site/resources/scripts/cssutilities/

IE6'ya kadar tüm modern tarayıcılarda çalışır, size Firebug gibi kural ve mülk koleksiyonları verebilir (aslında Firebug'dan daha doğrudur) ve ayrıca herhangi bir kuralın göreceli veya mutlak özgüllüğünü hesaplayabilir. Tek uyarı, statik medya türlerini anlamasına rağmen, medya sorgularını anlamamasıdır.


Bu modül gerçekten harika, umarım yazardan daha fazla sevgi alır.
mr1031011

4

İşte SB'nin yanıtının, eşleşen medya sorguları içinde eşleşen kuralları da döndüren bir versiyonu. *.rules || *.cssRulesBirleştirmeyi ve .matchesuygulama bulucuyu kaldırdım ; bir polyfill ekleyin veya ihtiyacınız olursa bu satırları tekrar ekleyin.

Bu sürüm CSSStyleRule, kural metni yerine nesneleri de döndürür . Bence bu biraz daha kullanışlı, çünkü kuralların özellikleri bu yolla programlı olarak daha kolay incelenebilir.

Kahve:

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

Bu, vefat edenlerin çocukları üzerinde kullanılmak üzere nasıl değiştirilebilir element?
Kragalon

2
Kullanım durumunuz nedir? Çocuklar için geçerli olan kurallar mutlaka ebeveyn için geçerli olmadığından, bunun nerede yararlı olacağını gerçekten anlamıyorum. Ortada hiçbir ortak yanı olmayan bir dizi kuralla sonuçlanırsınız. Eğer gerçekten istiyorsanız, sadece çocukları tekrarlayıp her biri için bu yöntemi çalıştırıp tüm sonuçların bir dizisini oluşturabilirsiniz.
2016

Ben sadece cloneNode(true)işlevsellik yapmaya çalışıyorum, ancak stil derinlemesine klonlanmış.
Kragalon

1
bu koşul: if (window.matchMedia (rule.conditionText) .matches) {...}, "rule.conditionText" tanımsız olduğundan benim durumumda bir eşleşmeyi engelledi. Onsuz işe yaradı. Bunu news.ycombinator.com adresinde deneyebilir ve test edebilirsiniz . "span.pagetop b", işlevinizle olduğu gibi eşleşmeyen bir medya sorgu kuralına sahiptir.
ayal gelles

1
Chrome, CSSMediaRule örneklerinde conditionText özelliğini desteklemez.
Macil

3

İşte sorguyu getMatchedCSSRulesdestekleyen işlev @mediasürümüm.

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
Cevabımın eski bir versiyonunun anlamsız kopyası . Sadece sayfayı kirletiyor. Eksiksiz ve güncel sürüm: burada .
7vujy0f0hy

1

IE9 + 'dan emin olarak, istenen eleman ve alt öğeleri için CSS'yi hesaplayan ve gerekirse aşağıdaki snippet'te yeni bir className'e kaydetme imkanı veren bir işlev yazdım.

/**
  * @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;
}

Kullanım

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

2
1. Tüm computeStylesalt programı sadece ile değiştirebilirsiniz el => getComputedStyle(el).cssText. Kanıt: keman . 2. '.' + element.className tek bir sınıf adının varlığını varsaydığı için hatalı bir yapıdır. Geçerli yapı element.className.replace(/^| /g, '.'). 3. İşleviniz, yalnızca sınıflar dışında diğer CSS seçicilerinin olasılığını göz ardı eder. 4. Özyinelemeniz keyfi olarak bir düzeyle sınırlıdır (çocuklar ama torunlar değil). 5. Kullanım: yok getElementByClassName, sadece getElementsByClassName(bir dizi döndürür).
7vujy0f0hy

1

SB'nin cevabının bu noktada kabul edilmesi gerektiğini düşünüyorum ama kesin değil. Kaçırılabilecek bazı kurallar olacağından birkaç kez bahsedilmektedir. Bununla karşılaştığımda element.matches yerine document.querySelectorAll kullanmaya karar verdim. Tek şey, aradığınız öğeyle karşılaştırmak için bir tür benzersiz öğe tanımlamasına ihtiyacınız olmasıdır. Çoğu durumda, kimliğini benzersiz bir değere sahip olacak şekilde ayarlayarak bunun başarılabileceğini düşünüyorum. Eşleşen öğenin size ait olduğunu bu şekilde tanımlayabilirsiniz. Document.querySelectorAll sonucunu aradığınız öğeyle eşleştirmenin genel bir yolunu düşünebiliyorsanız, bu aslında getMatchedCSSRules'in eksiksiz bir çoklu dolgusu olacaktır.

Document.querySelectorAll için performansı muhtemelen element.matches'ten daha yavaş olduğu için kontrol ettim, ancak çoğu durumda sorun olmamalı. Yaklaşık 0.001 milisaniye sürdüğünü görüyorum.

Bunu yapabileceğini ilan eden CSSUtilities kitaplığını da buldum, ancak eski olduğunu ve bir süredir güncellenmediğini hissediyorum. Kaynak koduna baktığımda, gözden kaçırdığı durumlar olabileceğini düşünüyorum.


CSSUtilities gerçekten eskidir, ancak sözde durumlar için de kuralları döndürür (örneğin, üzerine gelme kurallarını döndürebilir). Henüz burada sözde durumu ele alan herhangi bir cevap bulamadım.
mr1031011
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.