How in JavaScript do you retrieve the styles that have been applied to an element, excluding the default user agent styles (so inline + stylesheet styles only).
Basically, all the user styles you can see in the Computed tab of your favorite developer tool:
No framework please, IE8+, Edge, Chrome and Firefox.
I am expecting the answer to be the result of getComputedStyle minus getDefaultComputedStyle, but in a cross browser way. Seeing that all developer tools are capable of doing it, there must be a solution :)
First, you need to select the element with querySelector . Then, you use getComputedStyle to get the element's styles. If you log style , you should see an object that contains every CSS property and their respective values. You can also see this object in Chrome's and Firefox's dev tools.
The style property returns the values of an element's style attribute. The style property returns a CSSStyleDeclaration object. The CSSStyleDeclaration object contains all inline styles properties for the element. It does not contain any style properties set in the <head> section or in any external style sheets.
Here's a function that gets all the CSS rules that have been applied to an element from either inline styles (HTML style
attribute) or stylesheets on the page. It also grabs relevant keyframes for CSS animations and the :active
, :hover
, ::before
, and ::after
selectors.
function getAppliedCssData(el) {
// we create a unique id so we can generate unique ids for renaming animations
let uniqueId = "id" + Math.random().toString().slice(2) + Math.random().toString().slice(2);
let allRules = [...document.styleSheets].map(s => {
let rules = [];
try { rules.push(...s.cssRules) } catch(e) {} // we ignore cross-domain stylesheets with restrictive CORs headers
return rules;
}).flat();
let styleRules = allRules.filter(rule => rule.type === CSSRule.STYLE_RULE)
let fontFaceRules = allRules.filter(rule => rule.type === CSSRule.FONT_FACE_RULE);
let keyframesRules = allRules.filter(rule => rule.type === CSSRule.KEYFRAMES_RULE);
let matchingDefaultRules = styleRules.filter(rule => el.matches(rule.selectorText));
let nonMatchingRules = styleRules.filter(rule => !el.matches(rule.selectorText));
let matchingHoverRules = nonMatchingRules.filter(rule => el.matches(rule.selectorText.replace(/ :/g, " *:").replace(/([^(])(:hover)\b/g, "$1")));
let matchingActiveRules = nonMatchingRules.filter(rule => el.matches(rule.selectorText.replace(/ :/g, " *:").replace(/([^(])(:active)\b/g, "$1")));
let matchingBeforeRules = nonMatchingRules.filter(rule => el.matches(rule.selectorText.replace(/ :/g, " *:").replace(/::before\b/g, "")));
let matchingAfterRules = nonMatchingRules.filter(rule => el.matches(rule.selectorText.replace(/ :/g, " *:").replace(/::after\b/g, "")));
let allMatchingStyleRules = [...matchingActiveRules, ...matchingDefaultRules, ...matchingHoverRules, ...matchingBeforeRules, ...matchingAfterRules];
let matchingAnimationNames = allMatchingStyleRules.map(rule => rule.style.animationName).filter(n => n.trim());
let matchingKeyframeRules = keyframesRules.filter(rule => matchingAnimationNames.includes(rule.name));
// make name changes before actually grabbing the style text of each type
allMatchingStyleRules.forEach(rule => rule.style.animationName = rule.style.animationName+uniqueId);
matchingKeyframeRules.forEach(rule => rule.name = rule.name+uniqueId);
let matchingDefaultStyles = matchingDefaultRules.map(rule => rule.cssText).map(r => r.split(/[{}]/g)[1].trim()).join(" ") + (el.getAttribute('style') || ""); // important to add these last because inline styles are meant to override stylesheet styles (unless !important is used)
let matchingHoverStyles = matchingHoverRules.map(rule => rule.cssText).map(r => r.split(/[{}]/g)[1].trim()).join(" ");
let matchingActiveStyles = matchingActiveRules.map(rule => rule.cssText).map(r => r.split(/[{}]/g)[1].trim()).join(" ");
let matchingBeforeStyles = matchingBeforeRules.map(rule => rule.cssText).map(r => r.split(/[{}]/g)[1].trim()).join(" ");
let matchingAfterStyles = matchingAfterRules.map(rule => rule.cssText).map(r => r.split(/[{}]/g)[1].trim()).join(" ");
let matchingKeyframeStyles = matchingKeyframeRules.map(rule => rule.cssText).join(" ");
// undo the rule name changes because this actually affects the whole document:
matchingKeyframeRules.forEach(rule => rule.name = rule.name.replace(uniqueId, ""));
allMatchingStyleRules.forEach(rule => rule.style.animationName = rule.style.animationName.replace(uniqueId, ""));
let data = {
uniqueId,
defaultStyles: matchingDefaultStyles,
hoverStyles: matchingHoverStyles,
activeStyles: matchingActiveStyles,
keyframeStyles: matchingKeyframeStyles,
beforeStyles: matchingBeforeStyles,
afterStyles: matchingAfterStyles,
}
return data;
}
The :focus
, :focus-within
and :visited
selectors are not included, but could be easily added.
There is a read only property of document called 'styleSheets'.
var styleSheetList = document.styleSheets;
https://developer.mozilla.org/en-US/docs/Web/API/Document/styleSheets
By using this, you can reach all the styles which are applied by the author.
There is a similar question about this but not a duplicate, in here:
Is it possible to check if certain CSS properties are defined inside the style tag with Javascript?
You can get the applied style from an element, excluding the default user agent styles using the accepted answer of that question i just mentioned.
That answer didn't supply the element's own style
attribute content, so i have improved the code a bit:
var proto = Element.prototype;
var slice = Function.call.bind(Array.prototype.slice);
var matches = Function.call.bind(proto.matchesSelector ||
proto.mozMatchesSelector || proto.webkitMatchesSelector ||
proto.msMatchesSelector || proto.oMatchesSelector);
// Returns true if a DOM Element matches a cssRule
var elementMatchCSSRule = function(element, cssRule) {
return matches(element, cssRule.selectorText);
};
// Returns true if a property is defined in a cssRule
var propertyInCSSRule = function(prop, cssRule) {
return prop in cssRule.style && cssRule.style[prop] !== "";
};
// Here we get the cssRules across all the stylesheets in one array
var cssRules = slice(document.styleSheets).reduce(function(rules, styleSheet) {
return rules.concat(slice(styleSheet.cssRules));
}, []);
var getAppliedCss = function(elm) {
// get only the css rules that matches that element
var elementRules = cssRules.filter(elementMatchCSSRule.bind(null, elm));
var rules =[];
if(elementRules.length) {
for(i = 0; i < elementRules.length; i++) {
var e = elementRules[i];
rules.push({
order:i,
text:e.cssText
})
}
}
if(elm.getAttribute('style')) {
rules.push({
order:elementRules.length,
text:elm.getAttribute('style')
})
}
return rules;
}
function showStyle(){
var styleSheetList = document.styleSheets;
// get a reference to an element, then...
var div1 = document.getElementById("div1");
var rules = getAppliedCss(div1);
var str = '';
for(i = 0; i < rules.length; i++) {
var r = rules[i];
str += '<br/>Style Order: ' + r.order + ' | Style Text: ' + r.text;
}
document.getElementById("p1").innerHTML = str;
}
#div1 {
float:left;
width:100px;
}
div {
text-align:center;
}
<div id="div1" style="font-size:14px;">
Lorem ipsum
</div>
<br/>
<br/>
<a href="javascript:;" onclick="showStyle()"> Show me the style. </a>
<p id="p1"><p>
All developer tools can cheat, because they have access to the default rules the browser they are built into applies.
I thought that the following approach might work.
div
or p
) as the one we are interested in.iframe
.p
element, for example, then appending to the body may be more efficient.
It seems to work reasonably well in practice. I have only tested this in Firefox and Chrome, but I think that it should work in other browsers too - except maybe for the fact that I used for...in and for...of, but one could easily rewrite that. Note that not just the properties you specify are reported, but also some properties that are influenced by the properties you do specify. For example, the border color matches the text color by design and is hence reported as different even when you only set color: white
.
To summarize, I have taken the example you posted in one of your comments and added a getNonDefaultStyles
function to it that I think does what you want. It can of course be modified to cache default styles of say, div
elements and thus be more efficient in repeated calls (because modifying the DOM is expensive), but it shows the gist.
The below snippet shows how the version can be implemented that appends an element to the body. Due to limitations on StackOverflow, it is not possible to show the iframe
version in a snippet. It is possible on JSFiddle. The below snippet can also be found in a Fiddle.
var textarea = document.getElementById("textarea"),
paragraph = document.getElementById("paragraph");
/**
* Computes applied styles, assuming no rules targeting a specific element.
*/
function getNonDefaultStyles(el) {
var styles = {},
computed = window.getComputedStyle(el),
notTargetedContainer = document.createElement('div'),
elVanilla = document.createElement(el.tagName);
document.body.appendChild(notTargetedContainer);
notTargetedContainer.appendChild(elVanilla);
var vanilla = window.getComputedStyle(elVanilla);
for (let key of computed) {
if (vanilla[key] !== computed[key]) {
styles[key] = computed[key];
}
}
document.body.removeChild(notTargetedContainer);
return styles;
}
var paragraphStyles = getNonDefaultStyles(paragraph);
for (let style in paragraphStyles) {
textarea.value += style + ": " + paragraphStyles[style] + "\n";
}
#paragraph {
background: red;
}
textarea {
width: 300px;
height: 400px;
}
<p id="paragraph" style="color: white">
I am a DIV
</p>
<p>
User styles:
</p>
<textarea id="textarea"></textarea>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With