I often find nice stylings on the web. To copy the CSS of a DOM element, I inspect that element with Google Chrome Developer Tools, look at the various CSS properties, and copy those manually to my own stylesheets.
Is it possible to easily export all CSS properties of a given DOM element?
Here is the code for an exportStyles() method that should return a CSS string including all inline and external styles for a given element, except default values (which was the main difficulty).
For example: console.log(someElement.exportStyles());
Since you are using Chrome, I did not bother making it compatible with IE.
Actually it just needs that the browsers supports the getComputedStyle(element) method.
Element.prototype.exportStyles = (function () {
// Mapping between tag names and css default values lookup tables. This allows to exclude default values in the result.
var defaultStylesByTagName = {};
// Styles inherited from style sheets will not be rendered for elements with these tag names
var noStyleTags = {"BASE":true,"HEAD":true,"HTML":true,"META":true,"NOFRAME":true,"NOSCRIPT":true,"PARAM":true,"SCRIPT":true,"STYLE":true,"TITLE":true};
// This list determines which css default values lookup tables are precomputed at load time
// Lookup tables for other tag names will be automatically built at runtime if needed
var tagNames = ["A","ABBR","ADDRESS","AREA","ARTICLE","ASIDE","AUDIO","B","BASE","BDI","BDO","BLOCKQUOTE","BODY","BR","BUTTON","CANVAS","CAPTION","CENTER","CITE","CODE","COL","COLGROUP","COMMAND","DATALIST","DD","DEL","DETAILS","DFN","DIV","DL","DT","EM","EMBED","FIELDSET","FIGCAPTION","FIGURE","FONT","FOOTER","FORM","H1","H2","H3","H4","H5","H6","HEAD","HEADER","HGROUP","HR","HTML","I","IFRAME","IMG","INPUT","INS","KBD","KEYGEN","LABEL","LEGEND","LI","LINK","MAP","MARK","MATH","MENU","META","METER","NAV","NOBR","NOSCRIPT","OBJECT","OL","OPTION","OPTGROUP","OUTPUT","P","PARAM","PRE","PROGRESS","Q","RP","RT","RUBY","S","SAMP","SCRIPT","SECTION","SELECT","SMALL","SOURCE","SPAN","STRONG","STYLE","SUB","SUMMARY","SUP","SVG","TABLE","TBODY","TD","TEXTAREA","TFOOT","TH","THEAD","TIME","TITLE","TR","TRACK","U","UL","VAR","VIDEO","WBR"];
// Precompute the lookup tables.
for (var i = 0; i < tagNames.length; i++) {
if(!noStyleTags[tagNames[i]]) {
defaultStylesByTagName[tagNames[i]] = computeDefaultStyleByTagName(tagNames[i]);
}
}
function computeDefaultStyleByTagName(tagName) {
var defaultStyle = {};
var element = document.body.appendChild(document.createElement(tagName));
var computedStyle = getComputedStyle(element);
for (var i = 0; i < computedStyle.length; i++) {
defaultStyle[computedStyle[i]] = computedStyle[computedStyle[i]];
}
document.body.removeChild(element);
return defaultStyle;
}
function getDefaultStyleByTagName(tagName) {
tagName = tagName.toUpperCase();
if (!defaultStylesByTagName[tagName]) {
defaultStylesByTagName[tagName] = computeDefaultStyleByTagName(tagName);
}
return defaultStylesByTagName[tagName];
}
return function exportStyles() {
if (this.nodeType !== Node.ELEMENT_NODE) {
throw new TypeError("The exportStyles method only works on elements, not on " + this.nodeType + " nodes.");
}
if (noStyleTags[this.tagName]) {
throw new TypeError("The exportStyles method does not work on " + this.tagName + " elements.");
}
var styles = {};
var computedStyle = getComputedStyle(this);
var defaultStyle = getDefaultStyleByTagName(this.tagName);
for (var i = 0; i < computedStyle.length; i++) {
var cssPropName = computedStyle[i];
if (computedStyle[cssPropName] !== defaultStyle[cssPropName]) {
styles[cssPropName] = computedStyle[cssPropName];
}
}
var a = ["{"];
for(var i in styles) {
a[a.length] = i + ": " + styles[i] + ";";
}
a[a.length] = "}"
return a.join("\r\n");
}
})();
This code is base on my answer for a slightly related question: Extract the current DOM and print it as a string, with styles intact
I'm quoting Doozer Blake's excellent answer, provided above as a comment. If you like this answer, please upvote his original comment above:
Not a direct answer, but with Chrome Developer Tools, you can click inside Styles or Computed Styles, hit Ctrl+A and then Ctrl+C to copy all the styles in those given areas. It's not perfect in the Style tab because it picks up some extra stuff. Better than selecting them one by one I guess. – Doozer Blake 3 hours ago
You can do the same using Firebug for Firefox, by using Firebug's "Computed" side panel.
There are a few ways to almost do this.
Have a look at FireDiff
Also have a look at cssUpdater This is for local CSS only]
And see this Q for more similar tools: Why can't I save CSS changes in Firebug?
Also this paid product claims to be able to do this: http://www.skybound.ca/
Related
First of all if you read this question and I did have any misunderstandings of how css works please let me know
I'm trying to add some global variables to my stylesheets and I want to do it with javascript (I thought there may be a way to use global variables without importing a css file containing those variables in each css file I'm creating):
let cssContent = `:root{ --mainColor:"#333";}
First I tried to create a new stylesheet file and put cssContent in there:
var blob = new Blob([cssContent]);
var url = URL.createObjectURL(blob);
var cssElement = document.createElement('link');
cssElement.setAttribute('rel', 'stylesheet');
cssElement.setAttribute('type', 'text/css');
cssElement.setAttribute('href', url);
Then to add this stylesheet to head, first I removed all exsisting stylesheets, add cssElement and then all the other removed stylesheets so the cssElement be the first stylesheet in head element.
var cssElements = Array.from(document.head.getElementsByTagName('link'))
.filter(link => link.getAttribute('rel') == 'stylesheet');
if (cssElements && cssElements.length > 0) {
cssElements.forEach(cssEl => {
document.head.removeChild(cssEl)
});
The behavior of removing and adding stylesheets works fine
But the :root element goes after the defined rule for body and does not apply:
Then I tried to add this variables to each css file instead of creating new one:
for (let index = 0; index < document.styleSheets.length; index++) {
document.styleSheets[index].insertRule(cssContent, 0);
}
Again same thing happened to previous approach, happens here too:
At last I tried to add this variables to each rule, but I couldn't find an approach
Is there any way to do this?
you can use these three:
document.documentElement.style.setProperty("--mainColor", "#333");
Element.setAttribute()
OR
document.documentElement.style.cssText = "--mainColor: #333";
Document.documentElement
OR
document.documentElement.setAttribute("style", "--mainColor: #333");
CSSStyleDeclaration.setProperty()
Many tools/APIs provide ways of selecting elements of specific classes or IDs. There's also possible to inspect the raw stylesheets loaded by the browser.
However, for browsers to render an element, they'll compile all CSS rules (possibly from different stylesheet files) and apply it to the element. This is what you see with Firebug or the WebKit Inspector - the full CSS inheritance tree for an element.
How can I reproduce this feature in pure JavaScript without requiring additional browser plugins?
Perhaps an example can provide some clarification for what I'm looking for:
<style type="text/css">
p { color :red; }
#description { font-size: 20px; }
</style>
<p id="description">Lorem ipsum</p>
Here the p#description element have two CSS rules applied: a red color and a font size of 20 px.
I would like to find the source from where these computed CSS rules originate from (color comes the p rule and so on).
Since this question currently doesn't have a lightweight (non-library), cross-browser compatible answer, I'll try to provide one:
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/
Calling css(document.getElementById('elementId')) will return an array with an element for each CSS rule that matches the passed element.
If you want to find out more specific information about each rule, check out the CSSRule object documentation.
Short version12 April 2017
Challenger appears.
var getMatchedCSSRules = (el, css = el.ownerDocument.styleSheets) =>
[].concat(...[...css].map(s => [...s.cssRules||[]])) /* 1 */
.filter(r => el.matches(r.selectorText)); /* 2 */
Line /* 1 */ builds a flat array of all rules.
Line /* 2 */ discards non-matching rules.
Based on function css(el) by #S.B. on the same page.
Example 1
var div = iframedoc.querySelector("#myelement");
var rules = getMatchedCSSRules(div, iframedoc.styleSheets);
console.log(rules[0].parentStyleSheet.ownerNode, rules[0].cssText);
Example 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>
Shortcomings
No media handling, no #import, #media.
No access to styles loaded from cross-domain stylesheets.
No sorting by selector “specificity” (order of importance).
No styles inherited from parents.
May not work with old or rudimentary browsers.
Not sure how it copes with pseudo-classes and pseudo-selectors but seems to fare okay.
Maybe I will address these shortcomings one day.
Long version12 August 2018
Here’s a much more comprehensive implementation taken from someone’s GitHub page
(forked from this original code, via Bugzilla). Written for Gecko and IE, but is rumoured to work also with Blink.
4 May 2017: The specificity calculator has had critical bugs which I have now fixed. (I can’t notify the authors because I don’t have a GitHub account.)
12 August 2018: Recent Chrome updates seem to have decoupled object scope (this) from methods assigned to independent variables. Therefore invocation matcher(selector) has stopped working. Replacing it by matcher.call(el, selector) has solved it.
// 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);
};
}
Fixed bugs
= match → += match
return re ? re.length : 0; → return matches ? matches.length : 0;
_matchesSelector(element, selector) → matchesSelector(element, selector)
matcher(selector) → matcher.call(el, selector)
EDIT: This answer is now deprecated and no longer works in Chrome 64+. Leaving for historical context. In fact that bug report links back to this question for alternative solutions to using this.
Seems I managed to answer my own question after another hour of research.
It's as simple as this:
window.getMatchedCSSRules(document.getElementById("description"))
(Works in WebKit/Chrome, possibly others too)
Have a look at this library, which does what was asked for: http://www.brothercake.com/site/resources/scripts/cssutilities/
It works in all modern browsers right back to IE6, can give you rule and property collections like Firebug (in fact it's more accurate than Firebug), and can also calculate the relative or absolute specificity of any rule. The only caveat is that, although it understands static media types, it doesn't understand media-queries.
Here is my version of getMatchedCSSRules function which support #media query.
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
}
Here's a version of S.B.'s answer which also returns matching rules within matching media queries. I've removed the *.rules || *.cssRules coalescence and the .matches implementation finder; add a polyfill or add those lines back in if you need them.
This version also returns the CSSStyleRule objects rather than the rule text. I think this is a little more useful, since the specifics of the rules can be more easily probed programmatically this way.
Coffee:
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;
}
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>
Ensuring IE9+, I wrote a function which calculates CSS for requested element and its children, and gives possibility to save it to a new className if needed in snippet below.
/**
* #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;
}
Usage
getElementStyles(document.getElementByClassName('.my-class'), '.dummy-class', 'width:100%;opaity:0.5;transform:scale(1.5);');
I think the answer from S.B. should be the accepted one at this point but it is not exact. It is mentioned a few times that there will be some rules that may be missed. Faced with that, I decided to use document.querySelectorAll instead of element.matches. The only thing is that you would need some kind of unique identification of elements to compare it to the one you are looking for. In most cases I think that is achievable by setting its id to have a unique value. That's how you can identify the matched element being yours. If you can think of a general way to match the result of document.querySelectorAll to the element you are looking for that would essentially be a complete polyfill of getMatchedCSSRules.
I checked the performance for document.querySelectorAll since it probably is slower than element.matches but in most cases it should not be a problem. I see that it takes about 0.001 milliseconds.
I also found CSSUtilities library that advertises that it can do this but I feel its old and has not been updated in a while. Looking at its source code, it makes me think there may be cases that it misses.
As the linked question is closed as a duplicate of this, I add an answer here instead.
The unanswered part 2: "Once I found the computed style, I want to know where it comes from"
By looping over the document.styleSheets, and looking at the getComputedStyle() before and after you modify it, you can detect what stylesheet is in use.
It's far from optimal, but at least it can detect if the rule you looking at is in use or not.
Here is an exemple:
<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>
Many tools/APIs provide ways of selecting elements of specific classes or IDs. There's also possible to inspect the raw stylesheets loaded by the browser.
However, for browsers to render an element, they'll compile all CSS rules (possibly from different stylesheet files) and apply it to the element. This is what you see with Firebug or the WebKit Inspector - the full CSS inheritance tree for an element.
How can I reproduce this feature in pure JavaScript without requiring additional browser plugins?
Perhaps an example can provide some clarification for what I'm looking for:
<style type="text/css">
p { color :red; }
#description { font-size: 20px; }
</style>
<p id="description">Lorem ipsum</p>
Here the p#description element have two CSS rules applied: a red color and a font size of 20 px.
I would like to find the source from where these computed CSS rules originate from (color comes the p rule and so on).
Since this question currently doesn't have a lightweight (non-library), cross-browser compatible answer, I'll try to provide one:
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/
Calling css(document.getElementById('elementId')) will return an array with an element for each CSS rule that matches the passed element.
If you want to find out more specific information about each rule, check out the CSSRule object documentation.
Short version12 April 2017
Challenger appears.
var getMatchedCSSRules = (el, css = el.ownerDocument.styleSheets) =>
[].concat(...[...css].map(s => [...s.cssRules||[]])) /* 1 */
.filter(r => el.matches(r.selectorText)); /* 2 */
Line /* 1 */ builds a flat array of all rules.
Line /* 2 */ discards non-matching rules.
Based on function css(el) by #S.B. on the same page.
Example 1
var div = iframedoc.querySelector("#myelement");
var rules = getMatchedCSSRules(div, iframedoc.styleSheets);
console.log(rules[0].parentStyleSheet.ownerNode, rules[0].cssText);
Example 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>
Shortcomings
No media handling, no #import, #media.
No access to styles loaded from cross-domain stylesheets.
No sorting by selector “specificity” (order of importance).
No styles inherited from parents.
May not work with old or rudimentary browsers.
Not sure how it copes with pseudo-classes and pseudo-selectors but seems to fare okay.
Maybe I will address these shortcomings one day.
Long version12 August 2018
Here’s a much more comprehensive implementation taken from someone’s GitHub page
(forked from this original code, via Bugzilla). Written for Gecko and IE, but is rumoured to work also with Blink.
4 May 2017: The specificity calculator has had critical bugs which I have now fixed. (I can’t notify the authors because I don’t have a GitHub account.)
12 August 2018: Recent Chrome updates seem to have decoupled object scope (this) from methods assigned to independent variables. Therefore invocation matcher(selector) has stopped working. Replacing it by matcher.call(el, selector) has solved it.
// 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);
};
}
Fixed bugs
= match → += match
return re ? re.length : 0; → return matches ? matches.length : 0;
_matchesSelector(element, selector) → matchesSelector(element, selector)
matcher(selector) → matcher.call(el, selector)
EDIT: This answer is now deprecated and no longer works in Chrome 64+. Leaving for historical context. In fact that bug report links back to this question for alternative solutions to using this.
Seems I managed to answer my own question after another hour of research.
It's as simple as this:
window.getMatchedCSSRules(document.getElementById("description"))
(Works in WebKit/Chrome, possibly others too)
Have a look at this library, which does what was asked for: http://www.brothercake.com/site/resources/scripts/cssutilities/
It works in all modern browsers right back to IE6, can give you rule and property collections like Firebug (in fact it's more accurate than Firebug), and can also calculate the relative or absolute specificity of any rule. The only caveat is that, although it understands static media types, it doesn't understand media-queries.
Here is my version of getMatchedCSSRules function which support #media query.
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
}
Here's a version of S.B.'s answer which also returns matching rules within matching media queries. I've removed the *.rules || *.cssRules coalescence and the .matches implementation finder; add a polyfill or add those lines back in if you need them.
This version also returns the CSSStyleRule objects rather than the rule text. I think this is a little more useful, since the specifics of the rules can be more easily probed programmatically this way.
Coffee:
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;
}
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>
Ensuring IE9+, I wrote a function which calculates CSS for requested element and its children, and gives possibility to save it to a new className if needed in snippet below.
/**
* #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;
}
Usage
getElementStyles(document.getElementByClassName('.my-class'), '.dummy-class', 'width:100%;opaity:0.5;transform:scale(1.5);');
I think the answer from S.B. should be the accepted one at this point but it is not exact. It is mentioned a few times that there will be some rules that may be missed. Faced with that, I decided to use document.querySelectorAll instead of element.matches. The only thing is that you would need some kind of unique identification of elements to compare it to the one you are looking for. In most cases I think that is achievable by setting its id to have a unique value. That's how you can identify the matched element being yours. If you can think of a general way to match the result of document.querySelectorAll to the element you are looking for that would essentially be a complete polyfill of getMatchedCSSRules.
I checked the performance for document.querySelectorAll since it probably is slower than element.matches but in most cases it should not be a problem. I see that it takes about 0.001 milliseconds.
I also found CSSUtilities library that advertises that it can do this but I feel its old and has not been updated in a while. Looking at its source code, it makes me think there may be cases that it misses.
As the linked question is closed as a duplicate of this, I add an answer here instead.
The unanswered part 2: "Once I found the computed style, I want to know where it comes from"
By looping over the document.styleSheets, and looking at the getComputedStyle() before and after you modify it, you can detect what stylesheet is in use.
It's far from optimal, but at least it can detect if the rule you looking at is in use or not.
Here is an exemple:
<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>
When messing around in the FireBug css panel, you change the their representation of the original css file. Like:
.myCssClass { width: 100px; }
However, if you add a jQuery line to this,
$(".myCssClass").css("width", "200px");
you end (of course) up with changing the style tag for this element and you see that your original width:100px has a strikethough in the FireBug representation.
So my question is, do you know a way to change the "original" width:100px instead of changing the style tag. I guess you have to through a FireBug extension to access that property, and that is not a problem for me. But I don't know where to start :)
Edit: Have to point out that I am need to change the property by code! Either from a FireBug extension or somehow reload the corresponding css so that FireBug think it is the orginal value.
Here is an old JS function that usually worked well for me (Before Stylish and Greasemonkey).
Note that plain JS has security restrictions from accessing some stylesheets. A FF add-on can get around that, but then you need to also beware of corrupting browser-chrome styles.
function replaceStyleRuleByName (sStyleName, sNewRule)
{
var iNumStyleSheets = document.styleSheets.length;
var bDebug = 0;
if (bDebug) console.log ('There are ' + iNumStyleSheets + ' style sheets.');
for (iStyleS_Idx=0; iStyleS_Idx < iNumStyleSheets; iStyleS_Idx++)
{
var iNumRules = 0;
var zStyleSheet = document.styleSheets[iStyleS_Idx];
if (zStyleSheet)
{
/*---WARNING!
This next line can throw an uncaught exception!
Error: uncaught exception:
[Exception... "Access to restricted URI denied" code: "1012"
nsresult: "0x805303f4 (NS_ERROR_DOM_BAD_URI)"
location: ... ...]
*/
//--- try/catch for cross domain access issue.
try
{
var zRules = zStyleSheet.cssRules;
if (zRules)
{
iNumRules = zRules.length;
}
}
catch (e)
{// Just swallow the error for now.
}
}
if (bDebug) console.log ("Style sheet " + iStyleS_Idx + " has " + iNumRules + " ACCESSIBLE rules and src: " + zStyleSheet.href);
//for (var iRuleIdx=iNumRules-1; iRuleIdx >= 0; --iRuleIdx)
for (var iRuleIdx=0; iRuleIdx < iNumRules; ++iRuleIdx)
{
if (zRules[iRuleIdx].selectorText == sStyleName)
{
zStyleSheet.deleteRule (iRuleIdx);
if (bDebug) console.log (sNewRule);
if (sNewRule != null)
{
zStyleSheet.insertRule (sStyleName + sNewRule, iRuleIdx);
}
//return; //-- Sometimes changing just the first rule is not enough.
}
}
//--- Optional: Punt and add the rule, cold, to any accessible style sheet.
if (iNumRules > 0)
{
if (sNewRule != null)
{
try
{
zStyleSheet.insertRule (sStyleName + sNewRule, iRuleIdx);
}
catch(e)
{// Just swallow the error for now.
}
}
}
}
return;
}
Sample Usage:
replaceStyleRuleByName ('body', '{line-height: 1.5;}' );
replaceStyleRuleByName ('#adBox', '{display: none;}' );
replaceStyleRuleByName ('.BadStyle', null );
Just right click on the property in question and then edit [stylename]
Look for the "Computed" tab, it displays the actual values used of the properties of an element. The "Style" tab only displays the "stylesheet values" that affects a particular element, which may or may not be actually used by Firefox due to CSS' cascading rule and other layouting considerations.
Theres a mistake in my rather large demo where i assume all the divs under the class special will be used to align something. Now i realize i need to add an extra div outside of the part i want to align but inside of .special.
How do i write .special div[NOT someclass] ? or is there no way to do this and i need to rewrite a lot of html?
CSS3 includes the not() selector. The only problem is (you guessed it) no IE compatibility. If you're willing to require Javascript from IE <9 users, you can get IE compatibility with IE9.js.
+1 to both answers above.
I'll add i was able to get away with some things but writing this in the css block to undo the effect
some-type: inherit;
I would go with jQuery or some other Javascript Framework, the selectors just rock and NOT class XY is rather easy to achieve.
As Pekka pointed out I am not sure what brothers you want to target. getElementsByClassName() is implemented by almost all browsers (you know which one doesn't work, don't you?).
I found a rather nifty solution on devshed to also make it work in IE:
onload=function(){
if (document.getElementsByClassName == undefined) {
document.getElementsByClassName = function(className)
{
var hasClassName = new RegExp("(?:^|\\s)" + className + "(?:$|\\s)");
var allElements = document.getElementsByTagName("*");
var results = [];
var element;
for (var i = 0; (element = allElements[i]) != null; i++) {
var elementClass = element.className;
if (elementClass && elementClass.indexOf(className) != -1 && hasClassName.test(elementClass))
results.push(element);
}
return results;
}
}
}
All you need to do now is to iterate through all your div classes and negate the one you DON'T want.