使用 JavaScript 复制元素(及其样式)

Posted

技术标签:

【中文标题】使用 JavaScript 复制元素(及其样式)【英文标题】:Duplicating an element (and its style) with JavaScript 【发布时间】:2010-12-23 08:04:37 【问题描述】:

对于我正在实现的 javascript 库,我需要 克隆一个与原始应用样式完全相同的元素。虽然我已经获得了相当不错的 JavaScript 知识,但作为一种编程语言,在开发它时,我仍然是一个 DOM 脚本新手,所以任何关于如何实现这一点的建议都会非常有帮助(而且必须完成不使用任何其他 JavaScript 库)。

非常感谢您。

编辑:cloneNode(true) 不会克隆元素的计算样式。假设您有以下 html

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

还有一些风格,比如:

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

如果你只是克隆元素,使用类似的东西:

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

样式不会被克隆。

【问题讨论】:

【参考方案1】:

您不仅需要克隆,而且可能还需要进行深度克隆。

node.cloneNode(true);

文档是here。

如果 deep 设置为 false,则没有 子节点被克隆。任何文字 包含的节点未克隆 要么,因为它包含在一个或 更多子文本节点。

如果 deep 评估为真,则整个 子树(包括可能在 子文本节点)也被复制。为了 空节点(例如 IMG 和 INPUT 元素)是否无关紧要 deep 设置为 true 或 false 但您 仍然必须提供一个值。

编辑:OP 声明 node.cloneNode(true) 没有复制样式。下面是一个简单的测试,它使用 jQuery 和标准 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)); 

结果可见:http://jsbin.com/egice3/

编辑 2

希望你之前提到过;) 计算风格是完全不同的。更改您的 CSS 选择器或将该样式应用为一个类,您将获得解决方案。

编辑 3

因为这个问题是一个合法的问题,我没有找到任何好的解决方案,它让我很困扰,想出了以下问题。它不是特别优雅,但它可以完成工作(仅在 FF 3.5 中测试过)。

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);

有关更多信息和一些注意事项,请参阅 PPK 题为 Get Styles 的文章。

【讨论】:

感谢 Justing(和 S. Mark)的回复,但不幸的是,cloneNode(我使用它来克隆元素)对样式没有任何作用。这是我的问题的一部分:如何不仅克隆元素,还克隆应用于它的样式。 我可以发誓事实并非如此。让我做一两个测试。 不,恐怕不是,贾斯汀。 ;) 非常感谢您为完成上述示例所做的努力,但在我的测试中,样式没有被克隆(我已经编辑了我的原始问题以尝试显示它)。可能是因为您使用的是 jQuery 吗?或者因为 cloneNode 可能是克隆样式直接应用于 JavaScript 中的元素,而不是计算的元素? (我只是猜测。) 这不是因为使用了 jQuery。我同时使用了 jQuery 和标准 DOM 方法。 我明白了。所以这就是我对计算样式的想法,不是吗?关于您的第二次编辑——顺便说一句,我很抱歉在我的第一个问题中没有更具体;)——恐怕它不像你提出的解决方案那么容易,贾斯汀。当然,可以针对具体情况这样做,例如我的示例,但是我一直在寻找一种适用于每种情况的通用解决方案,无论样式是如何分配给原始元素的(如果它们是继承的,设置多个规则,使用不同的选择器等)。【参考方案2】:

这些都不适合我,但我是根据 Luigi 的回答想出这个的。

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];
        
    

【讨论】:

【参考方案3】:

在查看了整个 WEB 上的几个很好的解决方案后,我决定将每个解决方案的所有最佳方面结合起来并提出这个。

我将我的解决方案留在了简单的超快速 Javascript 中,这样每个人都可以翻译成他们本月最新的、很棒的 JS 风格。

代表来自马尼拉的香草.....


 * @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;
        
    

【讨论】:

神圣的大规模标记蝙蝠侠,这最终得到了 crazy 大 HTML。不过非常非常快,我+1 感谢 Dan - 正确,它确实会导致 HTML (CSS) 变大 - 它确实变成了一个怪物 - 它采用所有默认和应用的样式并复制它们。

以上是关于使用 JavaScript 复制元素(及其样式)的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript中DOM操作元素及节点操作

Javascript setInterval();不工作? [复制]

如何在 C# 中设置元素及其所有子元素的文本颜色和样式

为自己和孩子做出反应样式的组件选择器?

如何在 reactjs 中使用伪元素作为内联样式? [复制]

如何使用javascript更改内联另一个元素的样式?