m-use
m-use

Reputation: 363

JavaScript: getComputedStyle inconsistent return value between browsers

I am trying to get the value of the CSS left attribute like so... window.getComputedStyle(document.querySelector('.test'), ':after').getPropertyValue('left');

The problem is that this returns a pixel value in Chrome, but a percentage in FireFox. Is there any way to get it to force it to always return a pixel value?

JSFiddle: https://jsfiddle.net/r8mynvL4/2/

HTML:

<div class="test">This is a test.</div>

CSS:

.test {
  border: 1px solid black;
  position: relative;
}

.test:after {
  content: '*';
  bottom: -10%;
  left: 95%;
  position: absolute;
}

JS:

var leftValue = window.getComputedStyle(document.querySelector('.test'), ':after').getPropertyValue('left');

console.log(leftValue);

Upvotes: 3

Views: 1995

Answers (1)

Luke Briggs
Luke Briggs

Reputation: 3789

To directly answer the question, yes, try this method by e.generalov as described originally in a bugzilla report:

function getComputedStylePropertyValue(who, pseudo, propName) {
    var dv = who.ownerDocument.defaultView,
        cStyle = dv.getComputedStyle(who, pseudo),
        tmpNode = null, propVal;

    if (pseudo) {
        var css = cStyle.cssText;
        if (!css) { // it is an empty string. https://bugzilla.mozilla.org/show_bug.cgi?id=137687 
            var sty = [];
            for (var key in cStyle) {
                if (cStyle.hasOwnProperty(key)) {
                    sty.push(cStyle[key] + ':' + cStyle.getPropertyValue(cStyle[key]));
                }
            }
            css = sty.join(';');
        }
        tmpNode = document.createElement('dummy');
        tmpNode.style.cssText = css;
        if (pseudo === '::before' && who.firstChild) {
            who.insertBefore(tmpNode, who.firstChild);
        } else { // ::before in case of empty element or ::after
            who.appendChild(tmpNode);
        }
        cStyle = dv.getComputedStyle(tmpNode, null);
    }

    propVal = cStyle.getPropertyValue(propName);

    if (tmpNode) {
        tmpNode.parentNode.removeChild(tmpNode);
    }
    return propVal;
}

Here it is as a fiddle.

Which browser is correct?

It might surprise you that it's actually Gecko (Firefox) which appears to be correctly implementing the specification as it stands right now. (I say appears, because there may of course still be bugs in some parts of it). The quick summary is this part of the 'left' property definition:

Otherwise: if specified as a '<length>', the corresponding absolute length; if specified as a '<percentage>', the specified value; otherwise, auto.

Definition of the computed value - left property. "Otherwise" is applying because of the position:absolute declaration.

So, why doesn't Chrome do that? It seems fairly clear, right? Well, if you're interested in finding out how this standard became relatively inconsistent, read on!

The evolution of CSS - A battle of backwards compatibility vs. consistency

Firstly, newer user agents like Chrome don't need to bother much with backwards compatibility and can essentially ignore older and more obscure parts of the CSS specifications.

It's of course a different story for Gecko and the specifications themselves. CSS is versionless - your website doesn't declare which version of CSS it's using, for example. Instead, CSS modules organically build on top of each other and must be backwards compatible.

Meanwhile, Chrome favours consistency. It occasionally strays quite far from the strict definitions of the specifications with the reasoning being to push the web forward. The typical result is the specification gets updated to reflect anything that turned out well.

But Firefox is inconsistent with itself? Surely it's a bug?

This fiddle shows that non-pseudo elements do actually return px values. In short, this is attributed to the way in which CSS modules reference each other. It's the strict adherence to this referencing scheme that makes Firefox the de facto reference implementation from the W3C's point of view.

So, for example, the definition of getComputedStyle explicitly references CSS level 2:

This method is used to get the computed style as it is defined in [CSS2].

If we then proceed to take a look at the definition of a computed value as it is defined in there then we see that it once was defined in pixels:

percentages must be multiplied by a reference value (each property defines which value that is)

Meanwhile, pseudo-elements like ::before and ::after explicitly reference CSS3; they use the CSS3 description of left which now defines it differently.

Keep in mind that this specification is a working draft - it's not a requirement for browsers to follow it (but Firefox is doing so). Chrome on the other hand is pushing the consistency line - i.e. the whole API always only returns those used values.

Upvotes: 4

Related Questions