Muitas ferramentas / APIs fornecem maneiras de selecionar elementos de classes ou IDs específicos. Também é possível inspecionar as folhas de estilo brutas carregadas pelo navegador.
No entanto, para os navegadores renderizarem um elemento, eles compilarão todas as regras CSS (possivelmente de arquivos de folha de estilo diferentes) e as aplicarão ao elemento. Isso é o que você vê no Firebug ou no WebKit Inspector - a árvore de herança CSS completa para um elemento.
Como posso reproduzir esse recurso em JavaScript puro sem exigir plug-ins de navegador adicionais?
Talvez um exemplo possa fornecer algum esclarecimento sobre o que procuro:
<style type="text/css">
p { color :red; }
#description { font-size: 20px; }
</style>
<p id="description">Lorem ipsum</p>
Aqui, o elemento p # description tem duas regras CSS aplicadas: uma cor vermelha e um tamanho de fonte de 20 px.
Eu gostaria de encontrar a fonte de onde essas regras CSS calculadas se originam (a cor vem da regra p e assim por diante).
fonte
Respostas:
Como esta pergunta atualmente não tem uma resposta leve (não biblioteca) compatível com vários navegadores, tentarei fornecer uma:
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 chamada
css(document.getElementById('elementId'))
retornará uma matriz com um elemento para cada regra CSS que corresponda ao elemento passado. Se você deseja descobrir informações mais específicas sobre cada regra, verifique a documentação do objeto CSSRule .fonte
a.matches
é definido nesta linha:a.matches = a.matches || a.webkitMatchesSelector || a.mozMatchesSelector || a.msMatchesSelector || a.oMatchesSelector
. Isso significa que se já existe um método (padrão) de "correspondências" para nós DOM, ele o usará, caso contrário, tentará usar o específico do Webkit (webkitMatchesSelector), depois os do Mozilla, Microsoft e Opera. Você pode ler mais sobre isso aqui: developer.mozilla.org/en/docs/Web/API/Element/matchesif (a.matches(rules[r].selectorText))
condição de proteção.css()
cada um dos elementos pai.EDITAR: esta resposta está obsoleta e não funciona mais no Chrome 64+ . Saindo para o contexto histórico. Na verdade, esse relatório de bug leva de volta a esta questão para soluções alternativas para usar isso.
Parece que consegui responder minha própria pergunta depois de mais uma hora de pesquisa.
É tão simples quanto isto:
window.getMatchedCSSRules(document.getElementById("description"))
(Funciona no WebKit / Chrome, possivelmente em outros também)
fonte
Dê uma olhada nesta biblioteca, que faz o que foi pedido: http://www.brothercake.com/site/resources/scripts/cssutilities/
Ele funciona em todos os navegadores modernos desde o IE6, pode fornecer coleções de regras e propriedades como o Firebug (na verdade, é mais preciso do que o Firebug) e também pode calcular a especificidade relativa ou absoluta de qualquer regra. A única ressalva é que, embora entenda os tipos de mídia estática, não entende as consultas de mídia.
fonte
Versão curta 12 de abril de 2017
Challenger aparece.
var getMatchedCSSRules = (el, css = el.ownerDocument.styleSheets) => [].concat(...[...css].map(s => [...s.cssRules||[]])) /* 1 */ .filter(r => el.matches(r.selectorText)); /* 2 */
Line
/* 1 */
constrói uma matriz simples de todas as regras.A linha
/* 2 */
descarta as regras não correspondentes.Baseado na função
css(el)
de @SB na mesma página.Exemplo 1
var div = iframedoc.querySelector("#myelement"); var rules = getMatchedCSSRules(div, iframedoc.styleSheets); console.log(rules[0].parentStyleSheet.ownerNode, rules[0].cssText);
Exemplo 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>
Deficiências
@import
,@media
.Talvez eu resolva essas deficiências um dia.
Versão longa 12 de agosto de 2018
Aqui está uma implementação muito mais abrangente tirada da página GitHub de alguém (bifurcada deste código original , via Bugzilla ). Escrito para Gecko e IE, mas há rumores de que funcione também com o Blink.
4 de maio de 2017: A calculadora de especificidade apresentou bugs críticos que já corrigi. (Não posso notificar os autores porque não tenho uma conta no GitHub.)
12 de agosto de 2018: as atualizações recentes do Chrome parecem ter desacoplado o escopo do objeto (
this
) dos métodos atribuídos a variáveis independentes. Portanto, a invocaçãomatcher(selector)
parou de funcionar. Substituí-lo pormatcher.call(el, selector)
resolveu.// 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); }; }
Bugs corrigidos
= match
→+= match
return re ? re.length : 0;
→return matches ? matches.length : 0;
_matchesSelector(element, selector)
→matchesSelector(element, selector)
matcher(selector)
→matcher.call(el, selector)
fonte
Aqui está uma versão da resposta de SB que também retorna regras de correspondência dentro de consultas de mídia correspondentes. Eu removi a
*.rules || *.cssRules
coalescência e o.matches
localizador de implementação; adicione um polyfill ou adicione essas linhas de volta se precisar delas.Esta versão também retorna os
CSSStyleRule
objetos em vez do texto da regra. Acho que isso é um pouco mais útil, uma vez que as especificações das regras podem ser mais facilmente testadas programaticamente dessa maneira.Café:
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; }
fonte
element
?cloneNode(true)
funcionalidade, mas com a clonagem profunda de estilo também.Aqui está minha versão de
getMatchedCSSRules
função que suporta@media
consulta.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 }
fonte
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>
fonte
Garantindo o IE9 +, escrevi uma função que calcula CSS para o elemento solicitado e seus filhos, e dá a possibilidade de salvá-lo em um novo className se necessário no snippet abaixo.
/** * @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; }
Uso
getElementStyles(document.getElementByClassName('.my-class'), '.dummy-class', 'width:100%;opaity:0.5;transform:scale(1.5);');
fonte
computeStyles
sub-rotina por apenasel => getComputedStyle(el).cssText
. Prova: violino . 2.'.' + element.className
é uma construção falha porque assume a existência de um nome de classe. A construção válida éelement.className.replace(/^| /g, '.')
. 3. Sua função ignora a possibilidade de outros seletores CSS além das classes. 4. Sua recursão é arbitrariamente limitada a um nível (filhos, mas não netos). 5. Uso: não hágetElementByClassName
, apenasgetElementsByClassName
(retorna uma matriz).Acho que a resposta do SB deveria ser aceita neste momento, mas não é exata. É mencionado algumas vezes que algumas regras podem ser perdidas. Diante disso, decidi usar document.querySelectorAll em vez de element.matches. A única coisa é que você precisaria de algum tipo de identificação única de elementos para compará-lo com o que está procurando. Na maioria dos casos, acho que isso é possível definindo seu id para ter um valor único. É assim que você pode identificar o elemento correspondente sendo seu. Se você puder pensar em uma maneira geral de combinar o resultado de document.querySelectorAll com o elemento que está procurando, isso seria essencialmente um polyfill completo de getMatchedCSSRules.
Eu verifiquei o desempenho de document.querySelectorAll, pois provavelmente é mais lento do que element.matches, mas na maioria dos casos não deve ser um problema. Vejo que leva cerca de 0,001 milissegundos.
Eu também encontrei a biblioteca CSSUtilities que anuncia que pode fazer isso, mas eu sinto que é antiga e não é atualizada há algum tempo. Olhando para o seu código-fonte, me faz pensar que pode haver casos que ele pode perder.
fonte