Trova tutte le regole CSS che si applicano a un elemento


87

Molti strumenti / API forniscono modi per selezionare elementi di classi o ID specifici. È anche possibile ispezionare i fogli di stile grezzi caricati dal browser.

Tuttavia, affinché i browser visualizzino un elemento, compileranno tutte le regole CSS (possibilmente da diversi file di foglio di stile) e le applicheranno all'elemento. Questo è ciò che vedi con Firebug o WebKit Inspector: l'albero completo dell'ereditarietà CSS per un elemento.

Come posso riprodurre questa funzione in JavaScript puro senza richiedere plug-in del browser aggiuntivi?

Forse un esempio può fornire qualche chiarimento per quello che sto cercando:

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

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

Qui l'elemento p # description ha due regole CSS applicate: un colore rosso e una dimensione del carattere di 20 px.

Vorrei trovare la fonte da cui provengono queste regole CSS calcolate (il colore deriva dalla regola p e così via).



Visualizzare in un browser e utilizzare gli strumenti di sviluppo del browser (ad esempio la scheda Elementi in Chrome)?
Ronnie Royston

Risposte:


77

Poiché questa domanda attualmente non ha una risposta leggera (non di libreria) e compatibile con tutti i browser, cercherò di fornirne una:

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/

La chiamata css(document.getElementById('elementId'))restituirà un array con un elemento per ogni regola CSS che corrisponde all'elemento passato. Se vuoi trovare informazioni più specifiche su ciascuna regola, controlla la documentazione dell'oggetto CSSRule .


1
a.matchesè definita in questa linea: a.matches = a.matches || a.webkitMatchesSelector || a.mozMatchesSelector || a.msMatchesSelector || a.oMatchesSelector. Significa che se esiste già un metodo "match" (standard) per i nodi DOM, lo userà, altrimenti cerca di utilizzare quello specifico per Webkit (webkitMatchesSelector), quindi Mozilla, Microsoft e Opera. Puoi leggere di più al riguardo qui: developer.mozilla.org/en/docs/Web/API/Element/matches
SB

3
Sfortunatamente, penso che questa alternativa non rilevi tutte le regole CSS che ricadono dagli elementi padre nei bambini. Fiddle: jsfiddle.net/t554xo2L In questo caso, la regola UL (che si applica all'elemento) non è abbinata alla if (a.matches(rules[r].selectorText))condizione di protezione.
funforum

2
Non ho mai affermato di elencare / ereditare / regole CSS: tutto ciò che fa è elencare le regole CSS che corrispondono all'elemento passato. Se vuoi ottenere anche le regole ereditate per quell'elemento, probabilmente devi attraversare il DOM verso l'alto e chiamare css()ciascuno degli elementi padre.
SB

2
Lo so :-) Volevo solo sottolineare questo dato che le persone che potrebbero esaminare questa domanda potrebbero presumere che ottenga "tutte le regole CSS che si applicano a un elemento", come dice il titolo della domanda, il che non è il caso .
funforum

3
Se vuoi che tutte le regole siano attualmente applicate all'elemento, incluse quelle ereditate, dovresti usare getComputedStyle. Alla luce di ciò, penso che questa risposta sia corretta ed è giusto non includere stili ereditati dai genitori (il colore del testo assegnato al genitore, per esempio). Ciò che non include, tuttavia, sono le regole applicate in modo condizionale con le media query.
tremby

24

EDIT: questa risposta è ora deprecata e non funziona più in Chrome 64+ . Partendo per il contesto storico. In effetti, la segnalazione di bug rimanda a questa domanda per soluzioni alternative all'uso di questo.


Sembra che sono riuscito a rispondere alla mia domanda dopo un'altra ora di ricerca.

È così semplice:

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

(Funziona in WebKit / Chrome, forse anche in altri)


4
Bene, questo non è di grande utilità se è supportato solo da Chrome. Funzionerà per meno del 5% di tutti i visitatori (a seconda dei dati demografici).
Tomasi

5
@diamandiev: a giugno 2012, la quota di utilizzo di Chrome è aumentata a oltre il 32% (ed è leggermente superiore all'utilizzo di IE!). gs.statcounter.com
Roy Tinker,

6
getMatchedCSSRules NON mostra gli stili finali che si applicano all'elemento. Restituisce un array di tutti gli oggetti CSSStyleRule che si applicano nell'ordine in cui appaiono. Se esegui un responsive web design tramite query multimediali CSS o carichi più di un foglio di stile (come uno per IE), devi comunque scorrere ciascuno degli stili restituiti e calcolare la specificità css per ciascuna regola. Quindi calcola le regole finali applicabili. Devi riprodurre ciò che fa il browser in modo naturale. Per dimostrarlo nel tuo esempio, aggiungi "p {color: blue! Important}" all'inizio della tua dichiarazione di stile.
mrbinky3000


5
Questo è stato finalmente rimosso in Chrome 63 (post del blog ufficiale - che rimanda a questa domanda)
brichins

20

Versione breve 12 aprile 2017

Compare lo sfidante.

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

Line /* 1 */costruisce un array piatto di tutte le regole.
La riga /* 2 */ignora le regole non corrispondenti.

Basato sulla funzionecss(el) di @SB sulla stessa pagina.

Esempio 1

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

Esempio 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>

Mancanze

  • Nessuna manipolazione dei media, no @import, @media.
  • Nessun accesso agli stili caricati da fogli di stile interdominio.
  • Nessun ordinamento per "specificità" (ordine di importanza) del selettore.
  • Nessuno stile ereditato dai genitori.
  • Potrebbe non funzionare con browser vecchi o rudimentali.
  • Non sono sicuro di come affronti le pseudo-classi e gli pseudo-selettori, ma sembra che vada bene.

Forse un giorno affronterò queste carenze.

Versione lunga 12 agosto 2018

Ecco un'implementazione molto più completa presa dalla pagina GitHub di qualcuno (biforcuta da questo codice originale , tramite Bugzilla ). Scritto per Gecko e IE, ma si dice che lavori anche con i Blink.

4 maggio 2017: il calcolatore di specificità ha avuto bug critici che ora ho risolto. (Non posso avvisare gli autori perché non ho un account GitHub.)

12 agosto 2018: i recenti aggiornamenti di Chrome sembrano aver disaccoppiato l'ambito dell'oggetto ( this) dai metodi assegnati a variabili indipendenti. Pertanto l'invocazione matcher(selector)ha smesso di funzionare. Sostituirlo con lo matcher.call(el, selector)ha risolto.

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

Bug risolti

  • = match+= match
  • return re ? re.length : 0;return matches ? matches.length : 0;
  • _matchesSelector(element, selector)matchesSelector(element, selector)
  • matcher(selector)matcher.call(el, selector)

In getSheetRules ho dovuto aggiungere if (stylesheet.cssRules === null) {return []} per farlo funzionare per me.
Gwater17

Testato la "versione lunga". Per me va bene. Peccato che getMatchedCSSRules () non sia mai stato standardizzato dai browser.
colin moock

In che modo questo gestisce due selettori con le stesse specificità come, h1 e h1, div - dove dovrebbe essere usato quello dichiarato per ultimo?
Stellan

Forse possiamo farci un'idea per gestire lo pseudo qui? github.com/dvtng/jss/blob/master/jss.js
mr1031011

19

Dai un'occhiata a questa libreria, che fa ciò che è stato richiesto: http://www.brothercake.com/site/resources/scripts/cssutilities/

Funziona in tutti i browser moderni fino a IE6, può darti raccolte di regole e proprietà come Firebug (in effetti è più preciso di Firebug) e può anche calcolare la specificità relativa o assoluta di qualsiasi regola. L'unico avvertimento è che, sebbene comprenda i tipi di media statici, non comprende le media-query.


Questo modulo è davvero fantastico, spero solo che riceva più amore dall'autore.
mr1031011

4

Ecco una versione della risposta di SB che restituisce anche regole di corrispondenza all'interno di query multimediali corrispondenti. Ho rimosso la *.rules || *.cssRulescoalescenza e il .matchescercatore di implementazione; aggiungi un polyfill o aggiungi nuovamente quelle linee se ne hai bisogno.

Questa versione restituisce anche gli CSSStyleRuleoggetti anziché il testo della regola. Penso che questo sia un po 'più utile, poiché le specifiche delle regole possono essere più facilmente verificate a livello di programmazione in questo modo.

Caffè:

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

Come potrebbe essere cambiato per essere utilizzato anche sui bambini del passato element?
Kragalon

2
Qual è il tuo caso d'uso? Non vedo davvero dove sarebbe utile, poiché le regole che si applicano ai bambini non si applicano necessariamente al genitore. Ti ritroveresti con un mucchio di regole senza niente in particolare in comune. Se davvero lo vuoi, puoi semplicemente ricorrere ai bambini ed eseguire questo metodo per ciascuno, e costruire una serie di tutti i risultati.
tremby

Sto solo cercando di creare cloneNode(true)funzionalità, ma anche con uno stile profondo.
Kragalon

1
questa condizione: if (window.matchMedia (rule.conditionText) .matches) {...} ha impedito una corrispondenza nel mio caso poiché "rule.conditionText" era indefinito. Senza di essa, ha funzionato. Puoi provare a testarlo su news.ycombinator.com . "span.pagetop b" ha una regola di media query che non corrisponde alla tua funzione così com'è.
ayal gelles

1
Chrome non supporta la proprietà conditionText nelle istanze CSSMediaRule.
Macil

3

Ecco la mia versione della getMatchedCSSRulesfunzione che supporta la @mediaquery.

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
Duplice inutile di una vecchia versione della mia risposta. Sto solo inquinando la pagina. Versione completa e aggiornata: qui .
7vujy0f0hy

1

Garantendo IE9 +, ho scritto una funzione che calcola CSS per l'elemento richiesto e i suoi figli, e dà la possibilità di salvarlo in un nuovo className se necessario nello snippet di seguito.

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

Utilizzo

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

2
1. È possibile sostituire l'intera computeStylessubroutine semplicemente el => getComputedStyle(el).cssText. Prova: violino . 2. '.' + element.className è una costruzione difettosa perché presuppone l'esistenza di un nome di classe. La costruzione valida è element.className.replace(/^| /g, '.'). 3. La tua funzione ignora la possibilità di altri selettori CSS oltre alle semplici classi. 4. La tua ricorsione è arbitrariamente limitata a un livello (figli ma non nipoti). 5. Utilizzo: non c'è getElementByClassName, solo getElementsByClassName(restituisce un array).
7vujy0f0hy

1

Penso che la risposta di Sai Baba dovrebbe essere quella accettata a questo punto, ma non è esatta. È stato detto un paio di volte che ci saranno alcune regole che potrebbero essere ignorate. Di fronte a ciò, ho deciso di utilizzare document.querySelectorAll invece di element.matches. L'unica cosa è che avresti bisogno di un qualche tipo di identificazione univoca degli elementi per confrontarlo con quello che stai cercando. Nella maggior parte dei casi penso che sia ottenibile impostando il suo ID in modo che abbia un valore univoco. È così che puoi identificare l'elemento corrispondente che è tuo. Se riesci a pensare a un modo generale per abbinare il risultato di document.querySelectorAll all'elemento che stai cercando, sarebbe essenzialmente un polyfill completo di getMatchedCSSRules.

Ho controllato le prestazioni per document.querySelectorAll poiché probabilmente è più lento di element.matches ma nella maggior parte dei casi non dovrebbe essere un problema. Vedo che ci vogliono circa 0,001 millisecondi.

Ho anche trovato la libreria CSSUtilities che pubblicizza che può farlo, ma sento che è vecchia e non è stata aggiornata da un po '. Guardando il suo codice sorgente, mi fa pensare che potrebbero esserci dei casi in cui manca.


CSSUtilities è molto vecchio, ma restituisce anche le regole per gli pseudo stati (ad esempio, può restituire le regole del passaggio del mouse). Non ho ancora trovato alcuna risposta qui che affronti lo pseudo stato.
mr1031011
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.