Get element CSS property (width/height) value as it was set (in percent/em/px/etc)

How could one get an elements CSS property (for example width/height) as it was set with CSS rules, in whatever units it was set (eg percent/em/px)? (In Google Chrome, preferably frameworkless).

Using getComputedStyle returns the current value in pixels, so does css() in jQuery.

For example:

<div class="b">first</div>
<div id="a" class="a">second</div>

<style>
     div      { width: 100px; }
     x, div#a { width: 50%;   }
     .a       { width: 75%;   }
</style>

While iterating all div elements in this example, I'd like to be able to get the second divs width as 50% (and the first as 100px).


Chrome element inspector can display CSS property value as they were set, so it should be possible in Chrome.

Chrome element inspector showing property value as they were set


Not an exact duplicate of the linked question, as there the accepted answer there is a simple hack that produces a percentage width no matter what kind of width is set. And for the rest you have to know the selector used to make the active rule? How would one know that?

Answers:

Answer

It's not as simple as just calling WebKits getMatchedCSSRules(), it does return the matched rules in order of priority (altho I've seen no mention of this order in the docs), but the order does not take regard to property important priority and does not include element styles. So I ended up with this function:

getMatchedStyle

function getMatchedStyle(elem, property){
    // element property has highest priority
    var val = elem.style.getPropertyValue(property);

    // if it's important, we are done
    if(elem.style.getPropertyPriority(property))
        return val;

    // get matched rules
    var rules = getMatchedCSSRules(elem);

    // iterate the rules backwards
    // rules are ordered by priority, highest last
    for(var i = rules.length; i --> 0;){
        var r = rules[i];

        var important = r.style.getPropertyPriority(property);

        // if set, only reset if important
        if(val == null || important){
            val = r.style.getPropertyValue(property);

            // done if important
            if(important)
                break;
        }
    }

    return val;
}

Example

Given the following code and style rules:

<div class="b">div 1</div>
<div id="a" class="a d2">div 2</div>
<div id="b" class="b d3" style="width: 333px;">div 3</div>
<div id="c" class="c d4" style="width: 44em;">div 4</div>

<style>
div      { width: 100px; }
.d3      { width: auto !important; }
div#b    { width: 80%;   }
div#c.c  { width: 444px; }
x, div.a { width: 50%;   }
.a       { width: 75%;   }
</style>

this JS code

var d = document.querySelectorAll('div');

for(var i = 0; i < d.length; ++i){
    console.log("div " + (i+1) + ":  " + getMatchedStyle(d[i], 'width'));
}

gives the following widths for the divs:

div 1:  100px
div 2:  50%
div 3:  auto
div 4:  44em

(At jsFiddle)

Answer

Apparently there is no DOM API for this

https://developer.mozilla.org/en/DOM/window.getComputedStyle#Notes

EDIT: oops, just realized this was marked for Google Chrome

Try window.getMatchedCSSRules()

Answer

There's a newer duplicate post with a great answer here. That answer was for jQuery but it's easy to implement in pure js.

function getDefaultStyle(element, prop) {
    var parent = element.parentNode,
        computedStyle = getComputedStyle(element),
        value;
    parent.style.display = 'none';
    value = computedStyle.getPropertyValue(prop);
    parent.style.removeProperty('display');
    return value;
}
Answer

Good news everyone! There seems to be a CSS Typed OM on his way in the w3c drafts.

Fast reading this document, it seems that the goal of this maybe to-be specification, is to ease the access of CSSOM values from javascript.

The really important part of this for us here is that we will have a CSSUnitValue API, which will be able to parse CSS values to an object of the form

{
  value: 100,
  unit: "percent", // | "px" | "em" ...
  type: "percent"  // | "length"
}

And add a computedStyleMap() method, to the Element interface, from which we will be able to get the values actually applied on our elements.

As of today, only Chrome implements it (since 66).

(() => {
  if (!Element.prototype.computedStyleMap) {
    console.error("Your browser doesn't support CSS Typed OM");
    return;
  }
  document.querySelectorAll('.test')
    .forEach((elem) => {
      let styleMap = elem.computedStyleMap();
      const unitvalue = styleMap.get('width');
      console.log(elem, {
        type: unitvalue.type(),
        unit: unitvalue.unit,
        value: unitvalue.value
      });
    });

/* outputs

  <div class="b test">first</div> {
    "type": {
      "length": 1
    },
    "unit": "px",
    "value": 100
  }
  
  <div id="a" class="a test">second</div> {
    "type": {
      "percent": 1
    },
    "unit": "percent",
    "value": 50
  }

*/

})();
div.test {  width: 100px; }
x,div#a {  width: 50%; }
.a {  width: 75%; }
<div class="b test">first</div>
<div id="a" class="a test">second</div>

Answer

I'm surprised not to see this answer, so: You can get there by going through the stylesheets yourself and getting the information about the rules that match the element.

Here's a rough sketch of an example, using the specificity library to calculate selector specificity. getComputedStyle would tell you those sizes in pixels rather than the original units.

function applyStyles(target, style, specificity, appliedSpecs) {
    // Loop through its styles
    for (let [key, value] of Object.entries(style)) {
        // Skip the numerically-indexed ones giving us property names
        if (/^\d+$/.test(key)) {
            continue;
        }
        if (value !== "") {
            // Non-blank style. If it has !important, add to specificity.
            let thisSpec = specificity;
            if (style.getPropertyPriority(key) === "important") {
                // Important rule, so bump the first value (which will currently be 0
                // for a stylesheet style and 1 for an inline style
                thisSpec = [specificity[0] + 1, ...specificity.slice(1)];
            }
            // Non-blank style, do we have a style already and if so, with
            // what specificity?
            const currentSpec = appliedSpecs[key];
            if (!currentSpec || SPECIFICITY.compare(thisSpec, currentSpec) >= 0) {
                // Either we didn't already have this style or this new one
                // has the same or higher specificity and overrides it.
                target[key] = value;
                appliedSpecs[key] = thisSpec;
            }
        }
    }
}

function getDeclaredStyle(el) {
    // An object to fill in with styles
    const style = {};
    // An object to remember the specificity of the selector that set a style
    const appliedSpecs = {};
    // Loop through the sheets in order
    for (const sheet of Array.from(el.ownerDocument.styleSheets)) {
        // Loop through the rules
        const rules = sheet.cssRules || sheet.rules;
        if (rules) {
            for (const rule of Array.from(rules)) {
                const {selectorText} = rule;
                if (selectorText && el.matches(selectorText)) {
                    // This rule matches our element
                    if (rule.style) {
                        // Get the specificity of this rule
                        const specificity = SPECIFICITY.calculate(selectorText)[0].specificityArray;
                        // Apply these styles
                        applyStyles(style, rule.style, specificity, appliedSpecs);
                    }
                }
            }
        }
    }
    // Apply inline styles
    applyStyles(style, el.style, [0, 255, 255, 255], appliedSpecs);
    return style;
}

// Get the element
const el = document.querySelector("div.a.b");

// Get its declared style
const style = getDeclaredStyle(el);

// Height is 3em because .a.b is more specific than .a
console.log("height:      " + style.height);      // "3em"
// Width is 5em because of the !important flag; it overrides the inline style rule
console.log("width:       " + style.width);       // "5em"
// Border width is 1em because the rule is later than the other rules
console.log("line-height: " + style.lineHeight);  // "1.2"
// Color is blue because the inline style rule is !important
console.log("color:       " + style.color);       // "blue"

// Compare with results of `getComputedStyle`:
const computed = getComputedStyle(el);
console.log("computed height:      " + computed.height);
console.log("computed width:       " + computed.width);
console.log("computed line-height: " + computed.lineHeight);
console.log("completed color:      " + computed.color);
.a {
    width: 1em;
    height: 1em;
    width: 5em !important;
    color: red !important;
    line-height: 1.0;
    color: yellow !important;
}
.a.b {
    height: 3em;
}
.a {
    height: 2em;
    width: 4em;
    line-height: 1.2;
}
.as-console-wrapper {
    max-height: 100% !important;
}
<script src="//unpkg.com/[email protected]/dist/specificity.js"></script>
<div class="a b" style="width: 4em; color: blue !important">x</div>

Again, that's just a sketch, but it should head you the right way...

Here's an ES5 version:

// Get the element
var el = document.querySelector("div.a.b");
// An object to fill in with styles
var style = {};
// An object to remember the specificity of the selector that set a style
var specificity = {};
// Loop through the sheets in order
for (var sheetIndex = 0; sheetIndex < document.styleSheets.length; ++sheetIndex) {
    var sheet = document.styleSheets[sheetIndex];
    // Loop through the rules
    var rules = sheet.cssRules || sheet.rules;
    if (rules) {
        for (var ruleIndex = 0; ruleIndex < rules.length; ++ruleIndex) {
            var rule = rules[ruleIndex];
            var selectorText = rule.selectorText;
            if (selectorText && el.matches(selectorText)) {
                // This rule matches our element
                if (rule.style) {
                    // Get the specificity of this rule
                    var spec = SPECIFICITY.calculate(selectorText)[0].specificityArray;
                    // Loop through its styles
                    for (var key in rule.style) {
                        // Skip inherited ones and the numerically-indexed ones giving us property names
                        if (/^\d+$/.test(key) || !rule.style.hasOwnProperty(key)) {
                            continue;
                        }
                        var value = rule.style[key];
                        if (value !== "") {
                            // Non-blank style. If it has !important, add to specificity
                            var thisSpec = spec;
                            if (rule.style.getPropertyPriority(key) === "important") {
                                thisSpec = spec.slice();
                                thisSpec[0] = 1;
                            }
                            // Non-blank style, do we have a style already and if so, with
                            // what specificity?
                            var currentSpec = specificity[key];
                            if (!currentSpec || SPECIFICITY.compare(thisSpec, currentSpec) >= 0) {
                                // Either we didn't already have this style or this new one
                                // has the same or higher specificity and overrides it
                                style[key] = value;
                                specificity[key] = thisSpec;
                            }
                        }
                    }
                }
            }
        }
    }
}

// Height is 3em because .a.b is more specific than .a
console.log("height:      " + style.height);       // "3em"
// Width is 5em because of the !important flag
console.log("width:       " + style.width);       // "5em"
// Border width is 1em because the rule is later than the other rules
console.log("line-height: " + style.lineHeight); // "1.2"

// Compare with results of `getComputedStyle`:
var computed = getComputedStyle(el);
console.log("computed height:      " + computed.height);
console.log("computed width:       " + computed.width);
console.log("computed line-height: " + computed.lineHeight);
.a {
    height: 1em;
    width: 5em !important;
    line-height: 1.0;
}
.a.b {
    height: 3em;
}
.a {
    height: 2em;
    width: 4em;
    line-height: 1.2;
}
<script src="//unpkg.com/[email protected]/dist/specificity.js"></script>
<div class="a b"></div>

Note: The two big things the above doesn't do are:

  1. Handle styles inherited from an ancestor element. If you're interested in just a single property that you know is inherited, you could use the above and if it doesn't have the property set, repeat for the parent, etc. Or it's possible to extend this to apply inheritance based on the list of properties which says whether they're inherited or not and the rules of inheritance (being careful to allow for the inherit, initial, unset, and revert keywords, as well as the all keyword).

  2. Media queries. The snippet above just applies all rules with styles. It should check for CSSMediaRules, see if they match the current media (probably using matchMedia), and if so go into their cssRules and apply them. Probably not all that hard.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.