15

I'm trying to get the current runtime style of an element and filter out properties that have default values. For example, with markup like this:

<style>
    .foo { background: red }
    span { font-size:30px }
</style>


<div style="color: blue">
    <span id="bar" class="foo">hello</span>
</div>

I'd like the result to be:

 background-color: red;
 color: blue;
 font-size: 30px;

I tried window.getComputedStyle, but it returns a lot of stuff and I'm unsure how to filter out defaults. Any pointers will be appreciated.

gog
  • 10,367
  • 2
  • 24
  • 38

7 Answers7

16

there you go, i did this by adding a new dummy DOM element, to know which styles are default for any element.

/**
 * IE does not have `getComputedStyle` 
 */

window.getComputedStyle = window.getComputedStyle || function( element ) {
  return element.currentStyle;
}

/**
 * get computed style for an element, excluding any default styles
 *
 * @param {DOM} element
 * @return {object} difference
 */

function getStylesWithoutDefaults( element ) {

  // creating an empty dummy object to compare with
  var dummy = document.createElement( 'element-' + ( new Date().getTime() ) );
  document.body.appendChild( dummy );

  // getting computed styles for both elements
  var defaultStyles = getComputedStyle( dummy );
  var elementStyles = getComputedStyle( element );

  // calculating the difference
  var diff = {};
  for( var key in elementStyles ) {
    if(elementStyles.hasOwnProperty(key)
          && defaultStyles[ key ] !== elementStyles[ key ] )
    {
      diff[ key ] = elementStyles[ key ];
    }
  }

  // clear dom
  dummy.remove();

  return diff;
}


/**
 * usage
 */

console.log( getStylesWithoutDefaults( document.getElementById( 'bar' ) ) );

Notes:

  • the result will have some extra properties, not only those you've mentioned.

demo - console should be opened

Zaklinatel
  • 15
  • 6
Anas Nakawa
  • 1,977
  • 1
  • 23
  • 42
  • This won't work if the element itself is styled by custom css, e.g. `span {} `. – Etheryte Apr 07 '14 at 10:41
  • @Nit true, i'v updated the example to generate a dummy element with a random tagname to avoid this, thanks! – Anas Nakawa Apr 07 '14 at 10:49
  • I still don't think this does everything the original question asks about. You can't differentiate between the browser's default styles and what the user has defined on generic elements as far as I see. I can test and provide an example later, don't have a computer at hand. – Etheryte Apr 07 '14 at 11:09
  • 1
    Thank you for your solution. It worked for me, with some tweaks. – gog Apr 08 '14 at 08:44
  • 1
    Indeed, this doesn't quite do the job: If (for instance), the default stylesheet's background color is white, and the page's stylesheet has: `body { background-color: blue } .callout { background-color: white }` and the element we're testing has the `callout` class, this will mistakenly say that the element doesn't have a specific `background-color` style when, of course, it has one and it's important. – T.J. Crowder Aug 11 '14 at 08:04
  • Probably, `currnetStyle` should be `currentStyle`. – Oriol Jan 03 '15 at 16:06
  • 1
    If the user has defined any properties on body that are generic, or based on index, this won't work. I posted an answer here that solves the problem. – mattsven Nov 26 '16 at 02:14
  • using `const dummy = document.createElement(element.nodeName);` will be better. – KInGcC Sep 09 '19 at 10:32
3

Here's a more robust solution to this, using an iframe. This solution is inefficient for more than one element at a time, in which case you'll want to use a fragment to batch element insertion and pass in an array of tag names.

var getDefaultStyling = function(tagName){
    if(!tagName) tagName = "dummy-tag-name";

    //  Create dummy iframe

    var iframe = document.createElement("iframe");

    document.body.appendChild(iframe);

    //  Create element within the iframe's document

    var iframeDocument = iframe.contentDocument;
    var targetElement = iframeDocument.createElement(tagName);

    iframeDocument.body.appendChild(targetElement);

    //  Grab styling (CSSStyleDeclaration is live, and all values become "" after element removal)

    var styling = iframe.contentWindow.getComputedStyle(targetElement);
    var clonedStyling = {};

    for(var i = 0, len = styling.length; i < len; i++){
        var property = styling[i];

        clonedStyling[i] = property;
        clonedStyling[property] = styling[property];
    }

    //  Remove iframe

    document.body.removeChild(iframe);

    //  Return cloned styling

    return clonedStyling;
};

var getUniqueUserStyling = function(element){
    var allStyling = window.getComputedStyle(element);
    var defaultStyling = getDefaultStyling(element.tagName);

    var userStyling = {};

    for(var i = 0, len = allStyling.length; i < len; i++){
        var property = allStyling[i];
        var value = allStyling[property];
        var defaultValue = defaultStyling[property];

        if(value != defaultValue){
            userStyling[property] = value;
        }
    }

    return userStyling;
};

Usage: getUniqueUserStyling(myElement).

mattsven
  • 22,305
  • 11
  • 68
  • 104
  • I've added some improvements to the ` – Joseph238 May 11 '22 at 16:39
  • Note that the `getUserComputedStyle` function has one weakness: it cannot handle the case where the element inherits a style from its parent and sets the style back to the default. The only way to correctly handle this use case is via a more complex algorithm. For example, removing styles on the DOM tree to see if they change the computed value, like [this algorithm](https://github.com/1904labs/dom-to-image-more/issues/92#issuecomment-1361552316). To be fully robust, I believe it also would need to compare all computed values since some affect each other. – Joseph238 Dec 22 '22 at 23:05
3

I have built what I believe to be a more modern, complete and efficient solution, based on mattsven's answer.

All you have to do is call the getUserStyles method with the node as a parameter, as such: Styles.getUserStyles(document.querySelector('#bar'))

Obviously, this snippet was not made with older browser support in mind, so some adjustments should be made if you are to use this on a public website.

class Styles {
    // Returns a dummy iframe with no styles or content
    // This allows us to get default styles from the browser for an element
    static getStylesIframe() {
        if (typeof window.blankIframe != 'undefined') {
            return window.blankIframe;
        }

        window.blankIframe = document.createElement('iframe');
        document.body.appendChild(window.blankIframe);

        return window.blankIframe;
    }

    // Turns a CSSStyleDeclaration into a regular object, as all values become "" after a node is removed
    static getStylesObject(node, parentWindow) {
        const styles = parentWindow.getComputedStyle(node);
        let stylesObject = {};

        for (let i = 0; i < styles.length; i++) {
            const property = styles[i];
            stylesObject[property] = styles[property];
        }

        return stylesObject;
    }

    // Returns a styles object with the browser's default styles for the provided node
    static getDefaultStyles(node) {
        const iframe = Styles.getStylesIframe();
        const iframeDocument = iframe.contentDocument;
        const targetElement = iframeDocument.createElement(node.tagName);

        iframeDocument.body.appendChild(targetElement);
        const defaultStyles = Styles.getStylesObject(targetElement, iframe.contentWindow);

        targetElement.remove();

        return defaultStyles;
    }

    // Returns a styles object with only the styles applied by the user's CSS that differ from the browser's default styles
    static getUserStyles(node) {
        const defaultStyles = Styles.getDefaultStyles(node);
        const styles = Styles.getStylesObject(node, window);
        let userStyles = {};

        for (let property in defaultStyles) {
            if (styles[property] != defaultStyles[property]) {
                userStyles[property] = styles[property];
            }
        }

        return userStyles;
    }
};
Émile Perron
  • 813
  • 10
  • 26
0

Here's an option if you need to use getPropertyValue afterwards, like here

var computedStyles = getComputedStyles(document.body);
var propertyValue = computedStyles.getPropertyValue("prop-name");

Use this function:

function getDefaultComputedStyles(el)
{
    var temp = document.createElement("div");
    document.body.appendChild(temp);
    var defaultStyles = getComputedStyle(temp);
    var extraStyles = getComputedStyle(el);
    var foundStyles = [];

    for(var i=0; i<defaultStyles.length; i++)
    {
        var extraStyleIndex = extraStyles[i];
        var extraStyleValue = extraStyles.getPropertyValue(extraStyles[i]);
        if(defaultStyles.getPropertyValue(defaultStyles[i]) !== extraStyleValue)
        {
          foundStyles.push(JSON.parse(`{"${extraStyleIndex}":"${extraStyleValue}"}`));
        }
    }
    foundStyles.getPropertyValue = function(ind){
        var result = this.filter(el => (`${ind}` in el));
        return result[0]!=undefined ? result[0][Object.keys(result[0])] : null;
    }

    return foundStyles;
}
0

try this one on for size:

  1. Place these somewhere.

    <textarea id="t1"> </textarea> <textarea id="t2"> </textarea> <textarea id="t3"> </textarea> <textarea id="t4"> </textarea>

  2. next

function test() {
    let arr = [];
    let elem = _("right-Sidebar");
    let cStyl = window.getComputedStyle(elem);
    let txt1;

    for (x of cStyl) { txt1 += x + "=" + cStyl[x] + "\n"; }
    _("t1").value = txt1;
    let ss = document.getElementsByTagName("Style");
    const sstext = ss[0].innerHTML;
    ss[0].innerHTML = "";

    let elem2 = _("right-Sidebar");
    let cStyl2 = window.getComputedStyle(elem2);
    let txt2;

    for (x2 of cStyl2) { txt2 += x2 + "=" + cStyl2[x2] + "\n"; }
    _("t2").value = txt2;

    let div = _el(elem.tagName);
    let dfrag = new DocumentFragment();
    dfrag.appendChild(div);
    document.body.appendChild(dfrag);

    let cStyl3 = window.getComputedStyle(div);
    let txt3;

    for (x3 of cStyl3) { txt3 += x3 + "=" + cStyl3[x3] + "\n"; }
    _("t3").value = txt3;

    alert(_("t3").value === _("t2").value);


    let val1 = _("t2").value.split("\n");
    let val2 = _("t1").value.split("\n");


    let i = 0;
    while (i < val1.length) {

        if (val1[i] !== val2[i]) {
            arr.push(val1[i] + "\n" + val2[i] + "\n\n\n");
        }
        i++;
    }
    _("t4").value = "";
    _("t4").value = arr.join("\n");
}
IT goldman
  • 14,885
  • 2
  • 14
  • 28
-1

Here it is .. using pure javscript .. I only added jquery to the fiddle to set document.ready event.

Here is the code:

$(document).ready(function () {
    var div = document.getElementById('output');
    var x = document.getElementById('frame').contentWindow.document.createElement('x');
    document.getElementById('frame').contentWindow.document.body.appendChild(x);
    var defautlStyles = window.getComputedStyle(x);
    var barStyles = window.getComputedStyle(document.getElementById('bar'));
    for (i = 0; i < defautlStyles.length; i++) {
        if (defautlStyles["" + defautlStyles[i]] != barStyles["" + barStyles[i]]) {
            var p = document.createElement('p');
            p.innerText += barStyles[i] + ": " + barStyles["" + barStyles[i]];
            div.appendChild(p);
        }
    }
});

I used an iframe to add an element to it so the style of the added element won't be affected by the document default styles. And here is the FIDDLE

Hope it helps...

Karim AG
  • 2,166
  • 15
  • 29
  • Same problem as [the answer by Anas Nakawa](http://stackoverflow.com/a/22909984/1470607), you won't be able to differentiate between the default browser styles and element selector styles. – Etheryte Apr 07 '14 at 12:33
  • @Nit Change your browser default font and try this code .. it won't display the browser default font for you... – Karim AG Apr 07 '14 at 13:45
  • I see, but can you explain why ? – Karim AG Apr 07 '14 at 18:09
-2

You can use, e.g:

window.getComputedStyle(document.getElementById('bar'))

This will return an object containing all the computed styles for the element with the id 'bar'.

So, for example you could do:

var styles = window.getComputedStyle(document.getElementById('bar'));
styles = "background-color:" + styles['background-color'] + ";color:" + styles['color'] + ";font-size:" + styles['font-size'] + ";";
console.log(styles);

Note that color values will be returned in RGBA format.

Demo Fiddle

SW4
  • 69,876
  • 20
  • 132
  • 137
  • Thank you for your suggestion, however in my situation I don't know in advance which properties are set. – gog Apr 07 '14 at 09:57