34

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?

Qtax
  • 33,241
  • 9
  • 83
  • 121
  • 1
    this answer might be what you're looking for: http://stackoverflow.com/a/744450/684934 –  Mar 16 '12 at 01:29
  • Those answers are not fitting at all as I need to get the value somehow via the element, not on some specific selector. **Not exact duplicate!** – Qtax Mar 16 '12 at 01:37
  • % is only relevant in the context of its parent element so you'd need to work it out based on the comparative widths of the current&parent elements – Dan Mar 16 '12 at 02:30
  • $(element).style.width works for me..? – Dan Mar 16 '12 at 02:36
  • 1
    @Dan, no. `element.style.width` only returns a value if the element has a `style` attribute with `width` set. – Qtax Mar 16 '12 at 02:48
  • This method doesn’t work with vw / vh units https://jsfiddle.net/r6dwaoyb/2/ – 1213 Apr 02 '21 at 08:52

5 Answers5

20

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)

Qtax
  • 33,241
  • 9
  • 83
  • 121
  • Its a great function you have written, but it is returning only the inline style rules, not the rules defined in the css. How to get those? though it is a very helpful function. – Pramod Sep 29 '14 at 05:50
  • 1
    @Pramod, the function does return CSS values written in CSS rules (that's the whole point of it), as you can see if you look closer at the example. But it's only made for browsers which support `getMatchedCSSRules()`, that is Chrome and other WebKit/Blink based browsers (as was requested in the question). Too bad that more browsers don't support that. Altho Mozilla has a feature request to implement it: https://bugzilla.mozilla.org/show_bug.cgi?id=438278 – Qtax Sep 29 '14 at 07:03
  • 5
    @ChrisAllinson Sadly `getMatchedCSSRules()` doesn't work in Chrome any more. Blink has dropped this already, and webkit is talking about it. In the Chrome console you can see: *'getMatchedCSSRules()' is deprecated. For more help, check https://code.google.com/p/chromium/issues/detail?id=437569#c2* – Qtax Nov 11 '15 at 10:08
11

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>
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • Looking good. What result would your code example return for width? Could you add that to the answer so that it's easier to see what it does. – Qtax Mar 07 '18 at 15:12
  • 1
    @Qtax good point, I added the output as a comment in the code's snippet – Kaiido Mar 08 '18 at 01:31
5

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()

yas
  • 3,520
  • 4
  • 25
  • 38
3

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;
}
Sjeiti
  • 2,468
  • 1
  • 31
  • 33
  • 3
    This does not get the values in the same units as authored, but it does give you the `px`, `%` or `auto`, which may be good enough for some applications. So the trick is to set the parent to display none and get computed style. Interesting. +1 – Qtax Jul 01 '15 at 17:20
  • This is great... I'm surprised it works at all, but it doesn't seem to work for viewport units (vh, vw, etc). – Andrew Apr 20 '17 at 03:54
2

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/specificity@0.4.1/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/specificity@0.4.1/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.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 2
    This only returns the styles which are directly applied to an element(el in your case) not the inherited styles from the parent. It would be great if you can show an example where I can get all the styles. `getComputedStyle()` returns everything. – Alqama Bin Sadiq Apr 04 '20 at 14:48
  • 1
    @AlqamaBinSadiq - Oh, that's a good point. I've added a note to the answer about it. Maybe take the above and run with it, adding in inheritance? If not, I may do a simple library for this, but it'll have to go behind several other projects I have on my list... (Interesting to do, though.) – T.J. Crowder Apr 04 '20 at 15:02
  • That was I mentioned in the question that please do not mark it as duplicate I guess you marked my question and closed it. :( – Alqama Bin Sadiq Apr 04 '20 at 15:06
  • 1
    @AlqamaBinSadiq - Your question doesn't mention inheritance. The fact is, it **is** a duplicate of this question and others like it. It's just that there isn't a good answer. :-( – T.J. Crowder Apr 04 '20 at 15:07
  • I mentioned about the getComputedStyle because it returns everything. Thanks for your help though :) – Alqama Bin Sadiq Apr 04 '20 at 15:09
  • This doesn't seem to handle the HTML `style` attribute, but should be simple enough to add. Something like this is probably the best way to go if CSS Typed OM is not supported. +1 – Qtax Apr 06 '20 at 22:48
  • 1
    @Qtax - Doh! Fixed. Now if we can get someone who *really* understands CSS inheritance to help with that part, we'd be cooking with gas... I took a swing at it a couple of days ago, but I don't understand the subtleties of `revert`, `unset`, etc. well enough. – T.J. Crowder Apr 07 '20 at 07:52
  • 1
    @T.J.Crowder Also, it does not work with media query. – Alqama Bin Sadiq Apr 08 '20 at 19:54
  • @AlqamaBinSadiq - Yes, good point; I've flagged that up as well. – T.J. Crowder Apr 09 '20 at 07:08