交换两个HTML元素并保留它们上面的事件监听器

32

有类似的问题,但是所有的答案都只是针对内容内部的html元素进行交换。

我需要交换两个div,里面有很多内容(表格、选择框、输入框等),这些元素上有事件监听器,所以在交换主要div后,我需要保留它们。

我可以使用jQuery 1.5,因此使用它的答案可以接受。

9个回答

72

如果你想交换两个div,又不想丢失事件处理程序或破坏DOM引用,那么你可以只是在DOM中移动它们。关键是不要更改innerHTML,因为这会从头开始重新创建新的DOM节点,并且所有先前在这些DOM对象上的事件处理程序都将丢失。

但是,如果只是将DOM元素移到DOM中的新位置,则所有事件都会保持附加状态,因为DOM元素仅被重新分配父元素而没有改变DOM元素本身。

这是一个快速函数,可以交换DOM中的两个元素。只要一个不是另一个的子元素,它就可以使用:

function swapElements(obj1, obj2) {
    // create marker element and insert it where obj1 is
    var temp = document.createElement("div");
    obj1.parentNode.insertBefore(temp, obj1);

    // move obj1 to right before obj2
    obj2.parentNode.insertBefore(obj1, obj2);

    // move obj2 to right before where obj1 used to be
    temp.parentNode.insertBefore(obj2, temp);

    // remove temporary marker node
    temp.parentNode.removeChild(temp);
}
您可以在这里看到它的效果:http://jsfiddle.net/jfriend00/NThjN/
这是一个不需要插入临时元素就能工作的版本:
function swapElements(obj1, obj2) {
    // save the location of obj2
    var parent2 = obj2.parentNode;
    var next2 = obj2.nextSibling;
    // special case for obj1 is the next sibling of obj2
    if (next2 === obj1) {
        // just put obj1 before obj2
        parent2.insertBefore(obj1, obj2);
    } else {
        // insert obj2 right before obj1
        obj1.parentNode.insertBefore(obj2, obj1);

        // now insert obj1 where obj2 was
        if (next2) {
            // if there was an element after obj2, then insert obj1 right before that
            parent2.insertBefore(obj1, next2);
        } else {
            // otherwise, just append as last child
            parent2.appendChild(obj1);
        }
    }
}

示例演示:http://jsfiddle.net/jfriend00/oq92jqrb/


我想补充一点,这段代码是唯一一个真正有效的代码,除了两个特殊情况:交换前n个或后n个项目。 - philk
1
@philk - 我不明白你认为哪些边缘情况不起作用。请解释一下。也许可以展示一个 jsFiddle。 - jfriend00
这里的工作真的很出色!就性能而言,你建议做什么? - Jessica
1
@Jessica - 你需要在两者上运行类似于jsPerf的东西来测试性能,但我认为第二个(不创建临时元素的)更可能更快。 - jfriend00
关于第一个实现,使用replaceChild可以减少一个DOM操作,并且虚拟占位节点可以很容易地是一个文本节点、注释节点或处理指令节点,它们比元素节点要轻量得多(元素节点需要维护一个子节点列表、属性列表,并根据具体元素实现各种接口...)。至于第二个实现,你不需要单独处理next2 === nullinsertBefore会自动处理。 - undefined
显示剩余3条评论

7
这是一个更简单的函数,用于交换两个元素而无需重新加载元素...
function swapElements(obj1, obj2) {
    obj2.nextSibling === obj1
    ? obj1.parentNode.insertBefore(obj2, obj1.nextSibling)
    : obj1.parentNode.insertBefore(obj2, obj1); 
}

注意:如果obj1中有嵌入的像YouTube这样的视频,则在交换时不会重新加载。只是元素的位置发生了变化。

2
非常优雅,没有这里最被投票的标记div答案。 - philk
2
我相信在这个答案中,obj2必须在obj1之后。如果obj1obj2之前,它们将不会交换。@jfriend00的答案将交换两个元素,无论它们是彼此之前还是之后。 - aug
4
只有当元素紧挨着且具有相同的父级元素时,它才能正常工作。如果它们被几个元素分开或者属于不同的父级元素,则无法交换它们。此时它只是将其中一个移到另一个前面。可以尝试点击 此 jsFiddle 链接 并按下“Swap A C”按钮。 - jfriend00
非常优雅。在对列表进行排序时完美运作。 - user670839

1
我的简单函数似乎是最短且兼容性最广的。它不需要占位符、重新加载元素或任何混乱的东西:
function swapElements(el1, el2) {
    var p2 = el2.parentNode, n2 = el2.nextSibling
    if (n2 === el1) return p2.insertBefore(el1, el2)
    el1.parentNode.insertBefore(el2, el1);
    p2.insertBefore(el1, n2);
}

1

如果您跟踪这两个元素的父节点和其中一个元素的nextSibling,您可以交换两个带有其子元素的元素而不需要任何临时占位符。

如果第二个元素是唯一的子元素或其父元素的最后一个子元素,则其替换将(正确地)附加到父元素。

function swap(a, b){
    var p1= a.parentNode, p2= b.parentNode, sib= b.nextSibling;
    if(sib=== a) sib= sib.nextSibling;
    p1.replaceChild(b, a);
    if(sib) p2.insertBefore(a, sib);
    else p2.appendChild(a);
    return true;
}

0

由于问题中标记了jQuery,这里提供一个jQuery解决方案:

  $('#el_to_move').appendTo('#target_parent_el');

就是这样。jQuery 会将它剪切/复制到新位置。


这也可能会有帮助:

https://api.jquery.com/detach/


0

考虑到o1.swapNode(o2)在IE中已经很长时间运行良好,这一切都非常奇怪。在我混合了对象和文本节点的情况下,只有一个版本能够正常工作(已在此处显示):

let t = document.createElement("div");
o1.parentNode.insertBefore(t, o1);
o2.parentNode.insertBefore(o1, o2);
t.parentNode.insertBefore(o2, t);
t.parentNode.removeChild(t);

是的,我讨厌创建临时节点,但是如果您直接使用上面的版本(不调用函数),则没有这个技巧会失败。有时您不想调用函数。


0

这在一定程度上取决于你如何将事件绑定到要交换的元素上。如果你使用jQuery,那么this的答案就足够了。

最重要的是不要删除removeChildnode元素,只需将HTML复制粘贴到其他地方即可。这样做,一些浏览器会清除所有资源(包括事件处理程序),因此绑定的事件将丢失。虽然IE在动态绑定事件方面以泄漏内存而臭名昭著。真的很难预测各种浏览器的行为。

基本上,我想说:以任何你喜欢的方式交换元素,除了复制/粘贴HTML字符串之外,并使用jQuery的.live()方法绑定你的事件。因为我认为1.5没有.on方法。


复制并粘贴HTML到其他地方会创建全新的节点,这些节点不具有与原始节点相同的事件处理程序。因此,这种方法行不通。 - jfriend00
我知道,这就是为什么我说“以任何你喜欢的方式交换元素,除了复制/粘贴HTML字符串”。我强调了两次。在我看来,复制粘贴HTML是魔鬼的工作。想一想,在这种情况下,我建议克隆节点... - Elias Van Ootegem

-1

-1

如果你要交换相邻的元素,这真的很容易:

<div class="container">
   <div class="item">Item 1</div>
   <div class="item">Item 2</div>
</div>

$('.container .item:nth-child(2)').insertBefore('.container .item:nth-child(1)');

这段代码是jQuery的语法,它的作用是将容器中第二个元素移动到第一个元素之前。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接