68

I'm looking for a way using jQuery to return an object of computed styles for the 1st matched element. I could then pass this object to another call of jQuery's css method.

For example, with width, I can do the following to make the 2 divs have the same width:

$('#div2').width($('#div1').width());

It would be nice if I could make a text input look like an existing span:

$('#input1').css($('#span1').css());

where .css() with no argument returns an object that can be passed to .css(obj).

(I can't find a jQuery plugin for this, but it seems like it should exist. If it doesn't exist, I'll turn mine below into a plugin and post it with all the properties that I use.)

Basically, I want to pseudo clone certain elements but use a different tag. For example, I have an li element that I want to hide and put an input element over it that looks the same. When the user types, it looks like they are editing the element inline.

I'm also open to other approaches for this pseudo cloning problem for editing. Any suggestions?

Here's what I currently have. The only problem is just getting all the possible styles. This could be a ridiculously long list.


jQuery.fn.css2 = jQuery.fn.css;
jQuery.fn.css = function() {
    if (arguments.length) return jQuery.fn.css2.apply(this, arguments);
    var attr = ['font-family','font-size','font-weight','font-style','color',
    'text-transform','text-decoration','letter-spacing','word-spacing',
    'line-height','text-align','vertical-align','direction','background-color',
    'background-image','background-repeat','background-position',
    'background-attachment','opacity','width','height','top','right','bottom',
    'left','margin-top','margin-right','margin-bottom','margin-left',
    'padding-top','padding-right','padding-bottom','padding-left',
    'border-top-width','border-right-width','border-bottom-width',
    'border-left-width','border-top-color','border-right-color',
    'border-bottom-color','border-left-color','border-top-style',
    'border-right-style','border-bottom-style','border-left-style','position',
    'display','visibility','z-index','overflow-x','overflow-y','white-space',
    'clip','float','clear','cursor','list-style-image','list-style-position',
    'list-style-type','marker-offset'];
    var len = attr.length, obj = {};
    for (var i = 0; i < len; i++) 
        obj[attr[i]] = jQuery.fn.css2.call(this, attr[i]);
    return obj;
}

Edit: I've now been using the code above for awhile. It works well and behaves exactly like the original css method with one exception: if 0 args are passed, it returns the computed style object.

As you can see, it immediately calls the original css method if that's the case that applies. Otherwise, it gets the computed styles of all the listed properties (gathered from Firebug's computed style list). Although it's getting a long list of values, it's quite fast. Hope it's useful to others.

Keith Bentrup
  • 11,834
  • 7
  • 49
  • 56
  • 4
    I wonder if your question is better solved with CSS classes? – spender Jun 17 '09 at 00:03
  • I'd also like to see a solution to this, but I suggest not going through each computed style. When I use a non-jquery but standard method of getting computed styles, it takes about 1-1.5ms to get just one property. Running through an array fetching each property could add quite a sizable lag-time. – Ian Elliott Jun 17 '09 at 00:04
  • @Ian, profiling the above on my 2yr+ old laptop , it cloned about 50 properties in 7ms. – Keith Bentrup Jun 17 '09 at 02:23
  • possible duplicate of [Can jQuery get all CSS styles associated with an element?](http://stackoverflow.com/questions/754607/can-jquery-get-all-css-styles-associated-with-an-element) – alex Mar 08 '11 at 23:26
  • If you want to do this for a subtree of elements, then the question becomes could you compact the inlined styles, since computed is geared toward everything needed at each node. Any thoughts on this? – Rob May 29 '12 at 03:41
  • 1
    You don't need to pollute the `jQuery.fn` namespace with `css2`. You can just transform the original function in place if you use a closure. See my edit here: http://stackoverflow.com/a/1471256/399649 – Justin Morgan - On strike Apr 12 '13 at 16:43
  • 4 years later... Have you considered using SCSS or LESS to extend one element with another elements declared styles? – Patrick Berkeley Jul 17 '13 at 04:32

9 Answers9

62

Two years late, but I have the solution you're looking for. Here's a plugin I wrote (by wrapping another guy's function in plugin format) which does exactly what you want, but gets all possible styles in all browsers, even IE.

jquery.getStyleObject.js:

/*
 * getStyleObject Plugin for jQuery JavaScript Library
 * From: http://upshots.org/?p=112
 *
 * Copyright: Unknown, see source link
 * Plugin version by Dakota Schneider (http://hackthetruth.org)
 */

(function($){
    $.fn.getStyleObject = function(){
        var dom = this.get(0);
        var style;
        var returns = {};
        if(window.getComputedStyle){
            var camelize = function(a,b){
                return b.toUpperCase();
            }
            style = window.getComputedStyle(dom, null);
            for(var i=0;i<style.length;i++){
                var prop = style[i];
                var camel = prop.replace(/\-([a-z])/g, camelize);
                var val = style.getPropertyValue(prop);
                returns[camel] = val;
            }
            return returns;
        }
        if(dom.currentStyle){
            style = dom.currentStyle;
            for(var prop in style){
                returns[prop] = style[prop];
            }
            return returns;
        }
        return this.css();
    }
})(jQuery);

Basic usage is pretty simple:

var style = $("#original").getStyleObject(); // copy all computed CSS properties
$("#original").clone() // clone the object
    .parent() // select it's parent
    .appendTo() // append the cloned object to the parent, after the original
                // (though this could really be anywhere and ought to be somewhere
                // else to show that the styles aren't just inherited again
    .css(style); // apply cloned styles
starball
  • 20,030
  • 7
  • 43
  • 238
Dakota
  • 2,915
  • 2
  • 28
  • 25
  • 2
    Just need to change `var camel = prop.replace(/\-([a-z])/, camelize);` to `var camel = prop.replace(/\-([a-z])/g, camelize);` and it works beautifully. Thanks! – Ivan Jul 27 '11 at 20:07
  • 1
    @Dakota: +1. but I added parenthesis on the end - `getStyleObject();`; because style would otherwise contain a function and not an object with all styles ;) – Mottie Oct 15 '11 at 13:21
  • 1
    Any chance/way of getting this to work with newer, CSS3 properties, like transform, translate etc.? This doesn't do those – TrySpace Apr 19 '13 at 07:48
  • Is there any way to fetch all the inner elements too, with respective styles? The inner elements are not getting their styles. – Half Blood Prince May 20 '16 at 07:08
  • @HalfBloodPrince was looking for the same thing, see Nonconformist's answer on this page. – majick May 27 '16 at 19:00
  • @majick Yup. Working! Just one more thing. It gives me html source code in object form like `[Object object]`. How do I retrieve that as a string. – Half Blood Prince May 30 '16 at 05:15
  • @HalfBloodPrince ha I would like to know that one myself, I tried so many things (toArray, makeArray, etc.) and nothing seemed to work! weird...in the end I just made a copy of the entire function `getStyleObject` as `getStyleArray` and just changed `var returns = {};` to `var returns = new Array();` ... kinda silly but works fine. :-) ...then you can just loop the array eg. `for (key in array) {style = array[key];` - and note if you want to use the attribute key inside a style tag you will need to uncamelize it... – majick May 30 '16 at 05:30
  • @majick We can do this by `$("#id").cloneWithCSS().html();`. But not sure how to send this string to controller with ajax. – Half Blood Prince May 30 '16 at 06:12
23

It's not jQuery but, in Firefox, Opera and Safari you can use window.getComputedStyle(element) to get the computed styles for an element and in IE<=8 you can use element.currentStyle. The returned objects are different in each case, and I'm not sure how well either work with elements and styles created using Javascript, but perhaps they'll be useful.

In Safari you can do the following which is kind of neat:

document.getElementById('b').style.cssText = window.getComputedStyle(document.getElementById('a')).cssText;
abernier
  • 27,030
  • 20
  • 83
  • 114
Richard M
  • 14,244
  • 6
  • 52
  • 48
  • thanks. after looking into this problem more, these methods are used by jquery in the css method, so it would be rewriting what's already there. – Keith Bentrup Jun 21 '09 at 06:44
  • worth mentioning `window.getComputedStyle` is now quite well supported: http://caniuse.com/getcomputedstyle – abernier Nov 07 '13 at 23:05
5

I dont know if you're happy with the answers you got so far but I wasn't and mine may not please you either, but it may help someone else.

After pondering upon how to "clone" or "copy" elements' styles from one to another I have come to realize that it was not very optimal of an approach to loop through n and apply to n2, yet we're sorta stuck with this.

When you find yourself facing these issues, you rarely ever need to copy ALL the styles from one element to another... you usually have a specific reason to want "some" styles to apply.

Here's what I reverted to:

$.fn.copyCSS = function( style, toNode ){
  var self = $(this);
  if( !$.isArray( style ) ) style=style.split(' ');
  $.each( style, function( i, name ){ toNode.css( name, self.css(name) ) } );
  return self;
}

You can pass it a space-separated list of css attributes as the first argument and the node you want to clone them to as the second argument, like so:

$('div#copyFrom').copyCSS('width height color',$('div#copyTo'));

Whatever else seems to "misalign" after that, I'll try to fix with stylesheets as to not clutter my Js with too many misfired ideas.

Quickredfox
  • 1,428
  • 14
  • 20
4

I like your answer Quickredfox. I needed to copy some CSS but not immediately so I modified it to make the "toNode" optional.

$.fn.copyCSS = function( style, toNode ){
  var self = $(this),
   styleObj = {},
   has_toNode = typeof toNode != 'undefined' ? true: false;
 if( !$.isArray( style ) ) {
  style=style.split(' ');
 }
  $.each( style, function( i, name ){ 
  if(has_toNode) {
   toNode.css( name, self.css(name) );
  } else {
   styleObj[name] = self.css(name);
  }  
 });
  return ( has_toNode ? self : styleObj );
}

If you call it like this:

$('div#copyFrom').copyCSS('width height color');

Then it will return an object with your CSS declarations for you to use later:

{
 'width': '140px',
 'height': '860px',
 'color': 'rgb(238, 238, 238)'
}

Thanks for the starting point.

HexInteractive
  • 1,784
  • 13
  • 7
3

Now that I've had some time to look into the problem and understand better how jQuery's internal css method works, what I've posted seems to work well enough for the use case that I mentioned.

It's been proposed that you can solve this problem with CSS, but I think this is a more generalized solution that will work in any case without having to add an remove classes or update your css.

I hope others find it useful. If you find a bug, please let me know.

Keith Bentrup
  • 11,834
  • 7
  • 49
  • 56
3

Multipurpose .css()

Usage

$('body').css();        // -> { ... } - returns all styles
$('body').css('*');     // -> { ... } - the same (more verbose)
$('body').css('color width height')  // -> { color: .., width: .., height: .. } - returns requested styles
$('div').css('width height', '100%')  // set width and color to 100%, returns self
$('body').css('color')  // -> '#000' - native behaviour

Code

(function($) {

    // Monkey-patching original .css() method
    var nativeCss = $.fn.css;

    var camelCase = $.camelCase || function(str) {
        return str.replace(/\-([a-z])/g, function($0, $1) { return $1.toUpperCase(); });
    };

    $.fn.css = function(name, value) {
        if (name == null || name === '*') {
            var elem = this.get(0), css, returns = {};
            if (window.getComputedStyle) {
                css = window.getComputedStyle(elem, null);
                for (var i = 0, l = css.length; i < l; i++) {
                    returns[camelCase(css[i])] = css.getPropertyValue(css[i]);
                }
                return returns;
            } else if (elem.currentStyle) {
                css = elem.currentStyle;
                for (var prop in css) {
                    returns[prop] = css[prop];
                }
            }
            return returns;
        } else if (~name.indexOf(' ')) {
            var names = name.split(/ +/);
            var css = {};
            for (var i = 0, l = names.length; i < l; i++) {
                css[names[i]] = nativeCss.call(this, names[i], value);
            }
            return arguments.length > 1 ? this : css;
        } else {
            return nativeCss.apply(this, arguments);
        }
    }

})(jQuery);

Main idea is taken from Dakota's & HexInteractive's answers.

Community
  • 1
  • 1
disfated
  • 10,633
  • 12
  • 39
  • 50
  • Great function! But probably you need to add also a condition for undefined at the beginning: if (name == undefined || name == null || name === '*') to work properly. – gigaDIE Jun 20 '14 at 13:35
  • "name == null" handles undefined too. Сheck: undefined == null => returns true. – disfated Jun 20 '14 at 21:35
  • Thanks, adding **$('div').css('*:not(display)')** would come in very handy – Mike Jun 16 '16 at 20:27
3

I just wanted to add an extension to the code submitted by Dakota.

If you want to clone an element with all of the styles applied to it and all the children elements then you can use the following code:

/*
 * getStyleObject Plugin for jQuery JavaScript Library
 * From: http://upshots.org/?p=112
 *
 * Copyright: Unknown, see source link
 * Plugin version by Dakota Schneider (http://hackthetruth.org)
 */

(function($){
    $.fn.getStyleObject = function(){
        var dom = this.get(0);
        var style;
        var returns = {};
        if(window.getComputedStyle){
            var camelize = function(a,b){
                return b.toUpperCase();
            }
            style = window.getComputedStyle(dom, null);
            for(var i=0;i<style.length;i++){
                var prop = style[i];
                var camel = prop.replace(/\-([a-z])/g, camelize);
                var val = style.getPropertyValue(prop);
                returns[camel] = val;
            }
            return returns;
        }
        if(dom.currentStyle){
            style = dom.currentStyle;
            for(var prop in style){
                returns[prop] = style[prop];
            }
            return returns;
        }
        return this.css();
    }


    $.fn.cloneWithCSS = function() {
        var styles = {};

        var $this = $(this);
        var $clone = $this.clone();

        $clone.css( $this.getStyleObject() );

        var children = $this.children().toArray();
        var i = 0;
        while( children.length ) {
            var $child = $( children.pop() );
            styles[i++] = $child.getStyleObject();
            $child.children().each(function(i, el) {
                children.push(el);
            })
        }

        var cloneChildren = $clone.children().toArray()
        var i = 0;
        while( cloneChildren.length ) {
            var $child = $( cloneChildren.pop() );
            $child.css( styles[i++] );
            $child.children().each(function(i, el) {
                cloneChildren.push(el);
            })
        }

        return $clone
    }

})(jQuery);

Then you can just do: $clone = $("#target").cloneWithCSS()

Midas
  • 7,012
  • 5
  • 34
  • 52
marcopolo
  • 1,963
  • 4
  • 18
  • 31
2

Great function provided by the OP. I slightly modified it so that you can choose which values you want returned.

(function ($) {
    var jQuery_css = $.fn.css,
        gAttr = ['font-family','font-size','font-weight','font-style','color','text-transform','text-decoration','letter-spacing','word-spacing','line-height','text-align','vertical-align','direction','background-color','background-image','background-repeat','background-position','background-attachment','opacity','width','height','top','right','bottom','left','margin-top','margin-right','margin-bottom','margin-left','padding-top','padding-right','padding-bottom','padding-left','border-top-width','border-right-width','border-bottom-width','border-left-width','border-top-color','border-right-color','border-bottom-color','border-left-color','border-top-style','border-right-style','border-bottom-style','border-left-style','position','display','visibility','z-index','overflow-x','overflow-y','white-space','clip','float','clear','cursor','list-style-image','list-style-position','list-style-type','marker-offset'];
    $.fn.css = function() {
        if (arguments.length && !$.isArray(arguments[0])) return jQuery_css.apply(this, arguments);
        var attr = arguments[0] || gAttr,
            len = attr.length,
            obj = {};
        for (var i = 0; i < len; i++) obj[attr[i]] = jQuery_css.call(this, attr[i]);
        return obj;
    }
})(jQuery);

Choose which values you want by specifying your own array: $().css(['width','height']);

Shea
  • 1,965
  • 2
  • 18
  • 42
0
$.fn.cssCopy=function(element,styles){
var self=$(this);
if(element instanceof $){
    if(styles instanceof Array){
        $.each(styles,function(val){
            self.css(val,element.css(val));
        });
    }else if(typeof styles===”string”){
        self.css(styles,element.css(styles));
    }
}
return this;
};

Use example

$("#element").cssCopy($("#element2"),['width','height','border'])
rterrani
  • 526
  • 2
  • 10