39

How can I access a property from a CSS class by jQuery? I have a CSS class like:

.highlight { 
    color: red; 
}

And I need to do a color animation on an object:

$(this).animate({
    color: [color of highlight class]
}, 750);

So that I can change from red to blue (in the CSS) and my animation will work in accordance with my CSS.

One approach would be to place an invisible element with the highlight class and then get the color of the element to use in the animation, but I guess this is a very, very bad practice.

Any suggestions?

TylerH
  • 20,799
  • 66
  • 75
  • 101
robsonrosa
  • 2,548
  • 5
  • 23
  • 31
  • Why not just use typical CSS to change color. CSS3 has animation ability. I would recommend checking into using CSS3 rather resorting to jQuery. – Cam Jun 06 '13 at 15:03
  • 2
    @Cam old browser support for one. Sadly, not everyone can ignore IE8. – Benjamin Gruenbaum Jun 06 '13 at 15:03
  • 1
    You can loop through the rules in the styles sheets. Crazy solution can be found here: http://stackoverflow.com/questions/324486/how-do-you-read-css-rule-values-with-javascript The other solution is what you already suggested as long as there is not an !important somewhere else on a generic element selector. – epascarello Jun 06 '13 at 15:04
  • Why can't you store the color in a variable in javascript? – painotpi Jun 06 '13 at 15:07
  • 1
    I think creating an invisible element is probably your safest bet. If you only create one it's really not a big deal. Just remember that you need to create it, and then get the color, and then destroy it and not get the color from it every time since a call to `$(".highlight").css` would be able to override it otherwise. The only other solution would be to use a CSS parser, there are implementations, but limitations come with them since you'd need the actual CSS file in the same domain, and the flow would be crazy js->ajax to css->parse->get style. – Benjamin Gruenbaum Jun 06 '13 at 15:10
  • `window.getComputedStyle` (there's an equivalent for old-IE as well) – Prinzhorn Jun 06 '13 at 15:14
  • @Prinzhorn `getComputedStyle` works on an element, there is no element of that class, just the class itself. – Benjamin Gruenbaum Jun 06 '13 at 15:25
  • @BenjaminGruenbaum I see. – Prinzhorn Jun 06 '13 at 15:28
  • @badZoke if i could handle the same variable in the css file and in the javascript file it would be nice, but i guess that i can't do that. – robsonrosa Jun 06 '13 at 15:28
  • @epascarello it's a solution, but like you said, it's crazy, man. if i must choice between this and invisible element, i will use the second option. But thanks a lot. =) – robsonrosa Jun 06 '13 at 15:31

8 Answers8

77

I wrote a small function that traverses the stylesheets on the document looking for the matched selector, then style.

There is one caveat, this will only work for style sheets defined with a style tag, or external sheets from the same domain.

If the sheet is known you can pass it in and save yourself from having to look in multiple sheets (faster and if you have colliding rules it's more exact).

I only tested on jsFiddle with some weak test cases, let me know if this works for you.

function getStyleRuleValue(style, selector, sheet) {
    var sheets = typeof sheet !== 'undefined' ? [sheet] : document.styleSheets;
    for (var i = 0, l = sheets.length; i < l; i++) {
        var sheet = sheets[i];
        if( !sheet.cssRules ) { continue; }
        for (var j = 0, k = sheet.cssRules.length; j < k; j++) {
            var rule = sheet.cssRules[j];
            if (rule.selectorText && rule.selectorText.split(',').indexOf(selector) !== -1) {
                return rule.style[style];
            }
        }
    }
    return null;
}

example usage:

var color = getStyleRuleValue('color', '.foo'); // searches all sheets for the first .foo rule and returns the set color style.

var color = getStyleRuleValue('color', '.foo', document.styleSheets[2]); 

edit:

I neglected to take into consideration grouped rules. I changed the selector check to this:

if (rule.selectorText.split(',').indexOf(selector) !== -1) {

now it will check if any of the selectors in a grouped rules matches.

rlemon
  • 17,518
  • 14
  • 92
  • 123
  • 1
    +1 Very nice and done yourself. Also, note there is no jQuery dependency here so this can be used in any project! – Benjamin Gruenbaum Jun 06 '13 at 15:50
  • There were some issues when testing, should be all fixed now. Let me know if you find anything. – rlemon Jun 06 '13 at 15:52
  • @rlemon at my fourth sheet, the sheet.cssRules is null, so 'sheet.cssRules.length' throw an error. I don't know why it is null, but you could check if is null before the second 'for' – robsonrosa Jun 06 '13 at 16:10
  • added a check, didn't catch that one on jsfiddle :) – rlemon Jun 06 '13 at 16:14
  • i was testing with sheet.cssRules[ **i** ] :) – robsonrosa Jun 06 '13 at 17:10
  • @rlemon there is one problem. i have a rule like: .highlight { color: red; } // this is ok .highlight, important { color: red } // doesn't work i can fix it here in the css, but maybe you can solve it in your method too. – robsonrosa Jun 06 '13 at 17:34
  • sorry so you have an element named `important` ?? – rlemon Jun 06 '13 at 17:40
  • hehe, okok - i'll look into this. – rlemon Jun 06 '13 at 17:41
  • 1
    @robsonrosa I have two solutions that seem to work, let me test them and get back to you. – rlemon Jun 06 '13 at 17:58
  • sometimes 'rule.selectorText' is undefined and throws an error. i added another check in the last 'if'. it is awesome! =) – robsonrosa Jun 06 '13 at 19:13
  • @robsonrosa Updated the last check. Also, yea, like I mentioned I didn't fully test it. I will probably write up a blog post about it this week and really get into the test cases. possibly write fallback for external domain sheets. – rlemon Jun 06 '13 at 20:14
  • 1
    Useful function. But it still does not work for selectors like '.class, .class' because of spaces. Test: http://jsfiddle.net/C7BAB/. So it needs trim() for each selector. I tried to edit this answer. – And390 May 11 '14 at 19:35
  • I get why you added `rule.selectorText.indexOf(selector !== -1)`, but why `.split(",")` it? Won't these always return the same? – Trojan Jul 09 '14 at 23:23
  • @rlemon and about that blog? Did you have the post about this solution? – robsonrosa Feb 04 '15 at 13:12
  • This worked perfect for what I needed. Thank you for sharing this! – Dan Fletcher Jan 15 '16 at 19:11
  • As a complement to the answer I would add that to get the value of the css property "content" when it has something like "\f110" (encode used by FontAwsome, Gliphycons, etc) you can use `var content = escape(getStyleRuleValue('content', '.foo'));`. If you dont use it you will get this: `""` – fabriciorissetto Jun 03 '16 at 18:27
  • How to read from stylesheet which is not part of document but resides in project? – Archi Apr 12 '17 at 14:10
  • with your editor? I don't see how you could read (in the page) a sheet which isn't loaded into the page. – rlemon Apr 12 '17 at 14:16
3

Since you are already using jQuery try using the jQuery-ui's function switchClass which would allow you to animate to this new color.

For example:

 $('div').switchClass( "", "highlight", 1000 );

Demo


In case you do not want to include the whole UI library here is the code they use:

switchClass: function( remove, add, speed, easing, callback) {
    return $.effects.animateClass.call( this, {
        add: add,
        remove: remove
    }, speed, easing, callback );
}

And the animateClass fn:

var classAnimationActions = [ "add", "remove", "toggle" ],
    shorthandStyles = {
        border: 1,
        borderBottom: 1,
        borderColor: 1,
        borderLeft: 1,
        borderRight: 1,
        borderTop: 1,
        borderWidth: 1,
        margin: 1,
        padding: 1
    };
function styleDifference( oldStyle, newStyle ) {
    var diff = {},
        name, value;




    for ( name in newStyle ) {
        value = newStyle[ name ];
        if ( oldStyle[ name ] !== value ) {
            if ( !shorthandStyles[ name ] ) {
                if ( $.fx.step[ name ] || !isNaN( parseFloat( value ) ) ) {
                    diff[ name ] = value;
                }
            }
        }
    }




    return diff;
}
function getElementStyles( elem ) {
    var key, len,
        style = elem.ownerDocument.defaultView ?
            elem.ownerDocument.defaultView.getComputedStyle( elem, null ) :
            elem.currentStyle,
        styles = {};




    if ( style && style.length && style[ 0 ] && style[ style[ 0 ] ] ) {
        len = style.length;
        while ( len-- ) {
            key = style[ len ];
            if ( typeof style[ key ] === "string" ) {
                styles[ $.camelCase( key ) ] = style[ key ];
            }
        }
    // support: Opera, IE <9
    } else {
        for ( key in style ) {
            if ( typeof style[ key ] === "string" ) {
                styles[ key ] = style[ key ];
            }
        }
    }




    return styles;
}
$.effects.animateClass = function( value, duration, easing, callback ) {
    var o = $.speed( duration, easing, callback );

    return this.queue( function() {
        var animated = $( this ),
            baseClass = animated.attr( "class" ) || "",
            applyClassChange,
            allAnimations = o.children ? animated.find( "*" ).addBack() : animated;

        // map the animated objects to store the original styles.
        allAnimations = allAnimations.map(function() {
            var el = $( this );
            return {
                el: el,
                start: getElementStyles( this )
            };
        });

        // apply class change
        applyClassChange = function() {
            $.each( classAnimationActions, function(i, action) {
                if ( value[ action ] ) {
                    animated[ action + "Class" ]( value[ action ] );
                }
            });
        };
        applyClassChange();

        // map all animated objects again - calculate new styles and diff
        allAnimations = allAnimations.map(function() {
            this.end = getElementStyles( this.el[ 0 ] );
            this.diff = styleDifference( this.start, this.end );
            return this;
        });

        // apply original class
        animated.attr( "class", baseClass );

        // map all animated objects again - this time collecting a promise
        allAnimations = allAnimations.map(function() {
            var styleInfo = this,
                dfd = $.Deferred(),
                opts = $.extend({}, o, {
                    queue: false,
                    complete: function() {
                        dfd.resolve( styleInfo );
                    }
                });

            this.el.animate( this.diff, opts );
            return dfd.promise();
        });

        // once all animations have completed:
        $.when.apply( $, allAnimations.get() ).done(function() {

            // set the final class
            applyClassChange();

            // for each animated element,
            // clear all css properties that were animated
            $.each( arguments, function() {
                var el = this.el;
                $.each( this.diff, function(key) {
                    el.css( key, "" );
                });
            });

            // this is guarnteed to be there if you use jQuery.speed()
            // it also handles dequeuing the next anim...
            o.complete.call( animated[ 0 ] );
        });
    });
};

Working fiddle with all the functions that are needed: http://jsfiddle.net/maniator/3C5ZQ/

Naftali
  • 144,921
  • 39
  • 244
  • 303
2

How about this?

$('<span class="highlight"></span>').appendTo('body');
$(this).animate({
    color: $('.highlight').css('color')
}, 750);
$('.highlight').remove();

It's kind of dirty but will give you an (empty) element to reference to get the CSS value for which you are looking.

Update Here is a decent solution from CSS parser/abstracter? How to convert stylesheet into object

function findColorProperty(selector) {
    rules = document.styleSheets[0].cssRules
    for(i in rules) {
        //if(rules[i].selectorText==selector) 
            //return rules[i].cssText; // Original
        if(rules[i].selectorText == selector) 
            return rules[i].style.color; // Get color property specifically
    }
    return false;
}

Usage

$(this).animate({
    color: findColorProperty('.highlight')
}, 750);

Here is a fiddle http://jsfiddle.net/wzXDx/1/. I had to use styleSheets[1] to get this to work in the fiddle due to the embedded nature of the environment.

Community
  • 1
  • 1
Joe
  • 6,401
  • 3
  • 28
  • 32
  • don't forget to remove the added span :-P – Naftali Jun 06 '13 at 15:24
  • 1
    @BenjaminGruenbaum Updated to offer an actual solution. :) – Joe Jun 06 '13 at 15:46
  • That is a bad idea Joe... then you remove **all** highlights on the page and not the just the one u created. – Naftali Jun 06 '13 at 15:58
  • 1
    @Neal I thought the idea was that no `.highlight` existed on the page. If it did, wouldn't the OP just use `.css()` instead of all of this stylesheet parsing madness? – Joe Jun 06 '13 at 15:59
2

The only solution that come to my mind is the following:

//create an element with this class and append it to the DOM
var eleToGetColor = $('<div class="highlight" style="display: none;">').appendTo('body');
//get the color of the element
var color = eleToGetColor.css('color');
//completly remove the element from the DOM
eleToGetColor.remove();


$("div").animate({
  //set the new color
  color: color,
}, 1000);
.highlight {
  color: red;
}
div {
  width: 200px;
  height: 100px;
  color: blue;
  font-size: 6em;
  font-weight: bold;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.3/themes/smoothness/jquery-ui.css" />
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.3/jquery-ui.min.js"></script>
<div>TEST</div>
Alex Char
  • 32,879
  • 9
  • 49
  • 70
1

I've just wrote this function get all styles by a selector. Notice: The selector must be the same as in the CSS.

    /**
     * Gets styles by a classname
     * 
     * @notice The className must be 1:1 the same as in the CSS
     * @param string className_
     */
    function getStyle(className_) {

        var styleSheets = global_.document.styleSheets;
        var styleSheetsLength = styleSheets.length;
        for(var i = 0; i < styleSheetsLength; i++){
            var classes = styleSheets[i].rules || styleSheets[i].cssRules;
            var classesLength = classes.length;
            for (var x = 0; x < classesLength; x++) {
                if (classes[x].selectorText == className_) {
                    var ret;
                    if(classes[x].cssText){
                        ret = classes[x].cssText;
                    } else {
                        ret = classes[x].style.cssText;
                    }
                    if(ret.indexOf(classes[x].selectorText) == -1){
                        ret = classes[x].selectorText + "{" + ret + "}";
                    }
                    return ret;
                }
            }
        }

    }
dude
  • 5,678
  • 11
  • 54
  • 81
1

Here's another method: add a hidden div with the class applied. Use jQuery.css to look up the style value. Then remove the element.

http://plnkr.co/edit/Cu4lPbaJWHW42vgsk9ey

function getStyleValue(className, style) {
  var elementId = 'test-' + className,
    testElement = document.getElementById(elementId),
    val;

  if (testElement === null) {
    testElement = document.createElement('div');
    testElement.className = className;
    testElement.style.display = 'none';
    document.body.appendChild(testElement);
  }

  val = $(testElement).css(style);
  document.body.removeChild(testElement);
  return val;
}

console.log( 'The style value is ' + getStyleValue('dark-red', 'color') );
Tom Dunn
  • 2,425
  • 2
  • 13
  • 9
1

Why don't add .highlighted class, cache the color style, than remove it and animate to cached color? I.e. don't append elements and don't parse & loop styles.

jsfiddle example

var $element = $('.my-class').addClass('highlighted');
var colorToAnimate = $element.css('color');

$element.removeClass('highlighted');

alert(colorToAnimate);
.my-class {
  color: blue;
}
.highlighted {
  color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<span class="my-class">Animated color text</span>
Mo.
  • 26,306
  • 36
  • 159
  • 225
Лёша Ан
  • 273
  • 1
  • 2
  • 7
1

Unfortunately I can't comment on this awesome answer, but found a case that isn't catered for (when CSS class is declared multiple times and the first declaration doesn't have the style you're looking for), made a jsFiddle to cater for it:

function getStyleRuleValue(style, selector, sheet) {
  var sheets = typeof sheet !== 'undefined' ? [sheet] : document.styleSheets;
  for (var i = 0, l = sheets.length; i < l; i++) {
    var sheet = sheets[i];
    if( !sheet.cssRules ) { continue; }
    for (var j = 0, k = sheet.cssRules.length; j < k; j++) {
      var rule = sheet.cssRules[j];
      if (rule.selectorText && rule.selectorText.indexOf(selector) !== -1 && rule.style[style] !== '') {
        return rule.style[style];
      }
    }
  }
  return null;
}

Also took out the split in the conditional, not necessary and now it confirms that the style is present in the rule being checked.

Just for shigiggles created a jsFiddle to cache the styles by selector:

var styleCache = {};

function getStyleRuleValue(style, selector, sheet) {
  if (typeof styleCache[selector] !== 'undefined') {
    if (typeof styleCache[selector][style] !== 'undefined') {
        return styleCache[selector][style];
    }
  }
  else {
    styleCache[selector] = {};
  }

  var sheets = typeof sheet !== 'undefined' ? [sheet] : document.styleSheets;
  for (var i = 0, l = sheets.length; i < l; i++) {
    var sheet = sheets[i];
    if( !sheet.cssRules ) { continue; }
    for (var j = 0, k = sheet.cssRules.length; j < k; j++) {
      var rule = sheet.cssRules[j];
      if (rule.selectorText && rule.selectorText.indexOf(selector) !== -1 && rule.style[style] !== '') {
        return styleCache[selector][style] = rule.style[style];
      }
    }
  }
  return null;
}

Although if you do use that, I would suggest putting it in a closure/class. Thanks again to rlemon for the awesome original.

Community
  • 1
  • 1