Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get the applied style from an element, excluding the default user agent styles

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:

User styles displayed in Edge's 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 :)

like image 648
Gyum Fox Avatar asked Feb 03 '17 13:02

Gyum Fox


People also ask

How do you find the style of an element?

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.

What does element style do?

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.


3 Answers

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.

like image 51
joe Avatar answered Oct 13 '22 04:10

joe


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>
like image 40
er-han Avatar answered Oct 13 '22 04:10

er-han


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.

  1. Construct an element of exactly the same type (say, a div or p) as the one we are interested in.
  2. Append this element somewhere on the page so that only default browser rules are applied. We can do so by putting it in an iframe.
    If you are sure that you do not have rules targeting any p element, for example, then appending to the body may be more efficient.
  3. Check the difference in styles and only report values that differ.
  4. Clean up temporary element(s).

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>
like image 21
Just a student Avatar answered Oct 13 '22 02:10

Just a student