许多工具/ 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规则等等)。
答案 0 :(得分:65)
由于此问题目前没有轻量级(非库),跨浏览器兼容的答案,我将尝试提供一个:
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文档。
答案 1 :(得分:22)
编辑:此答案现已弃用,no longer works in Chrome 64+。留下历史背景。事实上,错误报告链接回到这个问题,以寻找使用它的替代解决方案。
似乎我在经过一个小时的研究后设法回答了我自己的问题。
这很简单:
window.getMatchedCSSRules(document.getElementById("description"))
(适用于WebKit / Chrome,也可能适用于其他人)
答案 2 :(得分:16)
查看这个库,它可以执行以下操作:http://www.brothercake.com/site/resources/scripts/cssutilities/
它适用于所有现代浏览器,直接回到IE6,可以为你提供像Firebug这样的规则和属性集合(实际上它比Firebug更准确),并且还可以计算任何规则的相对或绝对特异性。唯一需要注意的是,虽然它理解静态媒体类型,但它不了解媒体查询。
答案 3 :(得分:15)
挑战者出现了。
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)
。在同一页上。
var div = iframedoc.querySelector("#myelement");
var rules = getMatchedCSSRules(div, iframedoc.styleSheets);
console.log(rules[0].parentStyleSheet.ownerNode, rules[0].cssText);
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
。也许有一天我会解决这些缺点。
这是一个来自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)
答案 4 :(得分:3)
这是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;
}
答案 5 :(得分: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"));
&#13;
p {color: red;}
#description {font-size: 20px;}
&#13;
<p id="description">Lorem ipsum</p>
<textarea id="print" cols="50" rows="12"></textarea>
&#13;
答案 6 :(得分:1)
这是我的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
}
答案 7 :(得分:1)
我认为S.B.的答案在这一点上应该是公认的,但还不完全正确。几次提到可能会遗漏一些规则。面对这一点,我决定使用document.querySelectorAll代替element.matches。唯一的是,您需要某种独特的元素标识才能将其与您要查找的元素进行比较。在大多数情况下,我认为可以通过将其ID设置为具有唯一值来实现。这样便可以确定匹配的元素是您的。如果您可以想到一种将document.querySelectorAll的结果与您要查找的元素进行匹配的一般方法,则该方法实际上就是getMatchedCSSRules的完整polyfill。
我检查了document.querySelectorAll的性能,因为它可能比element.matches慢,但在大多数情况下应该不成问题。我看到它大约需要0.001毫秒。
我还找到了CSSUtilities库,该库宣传它可以执行此操作,但是我觉得它很旧,并且有一段时间没有更新。查看其源代码,它使我认为可能遗漏了某些情况。
答案 8 :(得分:0)
确保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);');
答案 9 :(得分:0)
由于 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>