41

For a JavaScript library I'm implementing, I need to clone an element which has exactly the same applied style than the original one. Although I've gained a rather decent knowledge of JavaScript, as a programming language, while developing it, I'm still a DOM scripting newbie, so any advice about how this can be achieved would be extremely helpful (and it has to be done without using any other JavaScript library).

Thank you very much in advance.

Edit: cloneNode(true) does not clone the computed style of the element. Let's say you have the following HTML:

<body>
  <p id="origin">This is the first paragraph.</p>
  <div id="destination">
    <p>The cloned paragraph is below:</p>
  </div>
</body>

And some style like:

body > p {
  font-size: 1.4em;
  font-family: Georgia;
  padding: 2em;
  background: rgb(165, 177, 33);
  color: rgb(66, 52, 49);
}

If you just clone the element, using something like:

var element = document.getElementById('origin');
var copy = element.cloneNode(true);
var destination = document.getElementById('destination');
destination.appendChild(copy);

Styles are not cloned.

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
acebal
  • 419
  • 1
  • 4
  • 6

3 Answers3

42

Not only will you need to clone, but you'll probably want to do deep cloning as well.

node.cloneNode(true);

Documentation is here.

If deep is set to false, none of the child nodes are cloned. Any text that the node contains is not cloned either, as it is contained in one or more child Text nodes.

If deep evaluates to true, the whole subtree (including text that may be in child Text nodes) is copied too. For empty nodes (e.g. IMG and INPUT elements) it doesn't matter whether deep is set to true or false but you still have to provide a value.

Edit: OP states that node.cloneNode(true) wasn't copying styles. Here is a simple test that shows the contrary (and the desired effect) using both jQuery and the standard DOM API:

var node = $("#d1");

// Add some arbitrary styles
node.css("height", "100px"); 
node.css("border", "1px solid red");

// jQuery clone
$("body").append(node.clone(true));

// Standard DOM clone (use node[0] to get to actual DOM node)
$("body").append(node[0].cloneNode(true)); 

Results are visible here: http://jsbin.com/egice3/

Edit 2

Wish you would have mentioned that before ;) Computed style is completely different. Change your CSS selector or apply that style as a class and you'll have a solution.

Edit 3

Because this problem is a legitimate one that I didn't find any good solutions for, it bothered me enough to come up with the following. It's not particularily graceful, but it gets the job done (tested in FF 3.5 only).

var realStyle = function(_elem, _style) {
    var computedStyle;
    if ( typeof _elem.currentStyle != 'undefined' ) {
        computedStyle = _elem.currentStyle;
    } else {
        computedStyle = document.defaultView.getComputedStyle(_elem, null);
    }

    return _style ? computedStyle[_style] : computedStyle;
};

var copyComputedStyle = function(src, dest) {
    var s = realStyle(src);
    for ( var i in s ) {
        // Do not use `hasOwnProperty`, nothing will get copied
        if ( typeof s[i] == "string" && s[i] && i != "cssText" && !/\d/.test(i) ) {
            // The try is for setter only properties
            try {
                dest.style[i] = s[i];
                // `fontSize` comes before `font` If `font` is empty, `fontSize` gets
                // overwritten.  So make sure to reset this property. (hackyhackhack)
                // Other properties may need similar treatment
                if ( i == "font" ) {
                    dest.style.fontSize = s.fontSize;
                }
            } catch (e) {}
        }
    }
};

var element = document.getElementById('origin');
var copy = element.cloneNode(true);
var destination = document.getElementById('destination');
destination.appendChild(copy);
copyComputedStyle(element, copy);

See PPK's article entitled Get Styles for more information and some caveats.

Derrick H
  • 510
  • 1
  • 9
  • 26
Justin Johnson
  • 30,978
  • 7
  • 65
  • 89
  • 2
    Thank you for the response, Justing (and S. Mark), but, unfortunately, cloneNode (which is what I am using for cloning the element) does nothing with regard to styles. That was the though part of my question: how to clone not only the element, but the styles applied to it. – acebal Dec 04 '09 at 21:14
  • I could have sworn that was not the case. Let me run a test or two. – Justin Johnson Dec 04 '09 at 23:12
  • 1
    No, I'm afraid it doesn't, Justin. ;) Thank you very much for the effort of doing the example above, but in my tests, styles are not being cloned (I've edited my original question to try to show it). Can it be due to you are using jQuery? Or because cloneNode perhaps is cloning styles applied directly to the element in JavaScript, but not the computed ones? (I'm just guessing.) – acebal Dec 05 '09 at 12:34
  • 1
    It's not due to using jQuery. I used both the jQuery and standard DOM method. – Justin Johnson Dec 05 '09 at 20:41
  • I see. So it's what I thought about computed styles, isn't it? With regard to your second edit –by the way, I'm sorry for haven't been more specific in my first question ;)–, I'm afraid it's not so easy as the solution you propose, Justin. Of course, it could be done so for a concrete situation, like that of my example, but I was looking for a general solution that works in every case, regardless of how the styles have been assigned to the original element (be them inherited, set in several rules, using different selectors, etcetera). – acebal Dec 06 '09 at 10:55
  • Would you like to see how I impelment DOM-mirror, which is a tutoruial to serialize all styles of all elements? http://github.com/aleen42/DOM-mirror – PuiMan Cheui Aug 13 '20 at 03:40
13

After looking at a couple of good solutions across the WEB, I decided to combine all the best aspects of each and come up with this.

I left my solution in plain super fast Javascript, so that everybody can translate to their latest and great JS flavour of the month.

Representing the vanilla from manilla.....


 * @problem: Sometimes .cloneNode(true) doesn't copy the styles and your are left
 * with everything copied but no styling applied to the clonedNode (it looks plain / ugly). Solution:
 * 
 * @solution: call synchronizeCssStyles to copy styles from source (src) element to
 * destination (dest) element.
 * 
 * @author: Luigi D'Amico (www.8bitplatoon.com)
 * 
 */
function synchronizeCssStyles(src, destination, recursively) {

    // if recursively = true, then we assume the src dom structure and destination dom structure are identical (ie: cloneNode was used)

    // window.getComputedStyle vs document.defaultView.getComputedStyle 
    // @TBD: also check for compatibility on IE/Edge 
    destination.style.cssText = document.defaultView.getComputedStyle(src, "").cssText;

    if (recursively) {
        var vSrcElements = src.getElementsByTagName("*");
        var vDstElements = destination.getElementsByTagName("*");

        for (var i = vSrcElements.length; i--;) {
            var vSrcElement = vSrcElements[i];
            var vDstElement = vDstElements[i];
//          console.log(i + " >> " + vSrcElement + " :: " + vDstElement);
            vDstElement.style.cssText = document.defaultView.getComputedStyle(vSrcElement, "").cssText;
        }
    }
}
Luigi D'Amico
  • 665
  • 7
  • 11
  • holy massive markup batman, this ends up with *crazy* big HTML. Very _very_ fast though, +1 from me – Dan F Mar 09 '18 at 05:47
  • 1
    Thanks Dan - correct it does result in big HTML (CSS) - it does become quite a monster - it takes all default and applied styles and duplicates them. – Luigi D'Amico Mar 10 '18 at 09:40
  • In latest Chrome (2022) `getComputedStyle(elem).cssText` returns `''` – Drenai Jun 20 '22 at 16:26
2

None of those worked for me, but I came up with this based on Luigi's answer.

copyStyles(source: HTMLElement, destination: HTMLElement) {

    // Get a list of all the source and destination elements
    const srcElements = <HTMLCollectionOf<HTMLElement>>source.getElementsByTagName('*');
    const dstElements = <HTMLCollectionOf<HTMLElement>>destination.getElementsByTagName('*');

    // For each element
    for (let i = srcElements.length; i--;) {
        const srcElement = srcElements[i];
        const dstElement = dstElements[i];
        const sourceElementStyles = document.defaultView.getComputedStyle(srcElement, '');
        const styleAttributeKeyNumbers = Object.keys(sourceElementStyles);

        // Copy the attribute
        for (let j = 0; j < styleAttributeKeyNumbers.length; j++) {
            const attributeKeyNumber = styleAttributeKeyNumbers[j];
            const attributeKey: string = sourceElementStyles[attributeKeyNumber];
            dstElement.style[attributeKey] = sourceElementStyles[attributeKey];
        }
    }
}
s3nt1n3lz
  • 96
  • 1
  • 5