有没有原生的jQuery函数可以切换元素?

189

我能否用jQuery轻松地交换两个元素?

如果可能的话,我希望用一行代码来实现这个功能。

我有一个选择元素和两个按钮,可以将选项向上或向下移动,我已经设置好了选定和目标选择器,我使用if语句来完成它,但我想知道是否有更简单的方法。


你能发布你的标记和代码示例吗? - Konstantin Tarkus
这不是jQuery而是JavaScript的问题:你不能在单个指令中交换DOM元素。然而,Paolo的答案是一个很棒的插件 ;) - Seb
1
对于从谷歌来到这里的人:请查看Lotif的答案,非常简单且完美地解决了我的问题。 - Maurice
1
@Maurice,我改变了被接受的答案。 - juan
1
这只会在元素相邻时交换它们!请修改已接受的答案。 - Serhiy
22个回答

254

以下是一种只使用 jQuery 解决此问题的有趣方法(如果这两个元素 相邻):

$("#element1").before($("#element2"));

或者

$("#element1").after($("#element2"));

15
我最喜欢这个选项!简单而且兼容性好。尽管我喜欢使用el1.insertBefore(el2)和el1.insertAfter(el2)来提高可读性。 - Maurice
6
但需要注意的是,由于我们在操作DOM,如果要移动的元素中含有iframe,则删除和添加可能会出现问题。iframe 将重新加载,并且重新加载到其原始网址而不是当前网址。这非常烦人,但与安全相关。我尚未找到解决方案。 - Maurice
214
除非这两个元素是相邻的,否则此函数不会交换它们。 - BonyT
3
只有当这些元素相邻时,它才会交换它们,否则Paolo的答案是正确的。 - Elmer
13
这个答案不应再被接受。它在一般情况下无法运行。 - thekingoftruth
显示剩余5条评论

82

保罗是对的,但我不确定为什么他要克隆相关元素。这并不是真正必要的,而且会丢失与元素及其后代相关联的任何引用或事件监听器。

以下是一个非克隆版本,使用普通的DOM方法(因为jQuery实际上没有任何特殊的函数让这个特定操作更容易):

function swapNodes(a, b) {
    var aparent = a.parentNode;
    var asibling = a.nextSibling === b ? a : a.nextSibling;
    b.parentNode.insertBefore(a, b);
    aparent.insertBefore(b, asibling);
}

2
http://docs.jquery.com/Clone - 传递参数 "true" 也会复制事件。我尝试了先不进行克隆但它所做的与你目前的一样:交换第一个和第二个并将第一个保留原位。 - Paolo Bergantino
3
啊,有一种特殊情况是a的下一个兄弟节点就是b本身。在上面的代码片段中已修复此问题。jQuery也在做同样的事情。 - bobince
我有一个非常注重顺序的列表,需要保留事件和ID,这个方法非常好用!谢谢! - Teekin
2
我觉得你应该添加一个jQuery版本来满足这个问题的标题。 :) - thekingoftruth
编译失败。"b.parentNode 未定义"。 - HoldOffHunger
显示剩余4条评论

73

没有现成的,但你可以自己动手写一个:

jQuery.fn.swapWith = function(to) {
    return this.each(function() {
        var copy_to = $(to).clone(true);
        var copy_from = $(this).clone(true);
        $(to).replaceWith(copy_from);
        $(this).replaceWith(copy_to);
    });
};

使用:

$(selector1).swapWith(selector2);

注意,这仅在选择器只匹配一个元素时有效,否则可能会产生奇怪的结果。


2
需要克隆它们吗? - juan
也许你可以直接使用html()写它们? - Ed James
2
如果这样做,我很确定你会失去那些元素上的绑定事件。 - alex
2
当div不相邻时,它的效果非常好 :) - Elmer
这应该是被接受的答案。它扩展了jQuery以提供所需的内容。 - Alice Wonder

50

这个问题有很多边界情况,被接受的答案或bobince的答案都没有处理。其他涉及克隆的解决方案走上了正确的轨道,但是克隆是昂贵且不必要的。我们想要克隆,因为有一个古老的问题,即如何交换两个变量,在其中一步中将一个变量分配给临时变量。在这种情况下,赋值(克隆)是不需要的。这里是一个基于jQuery的解决方案:

function swap(a, b) {
    a = $(a); b = $(b);
    var tmp = $('<span>').hide();
    a.before(tmp);
    b.before(a);
    tmp.replaceWith(b);
};

10
这应该是被接受的答案。克隆会移除任何事件触发器,是不必要的。我在我的代码中使用了这个确切的方法。 - thekingoftruth
3
这是更好的答案!我碰巧有一个 ul 作为我的交换元素的子元素,它是一个 jquery 可排序元素。在使用 Paolo Bergantino 的答案进行交换后,列表项无法再被拖放。但是使用 user2820356 的答案一切仍然正常运作。 - agoldev

27

jQuery的.before方法可以用于通过添加一个临时的第三个元素作为书签来交换元素 - 在移动元素时创建一个临时DOM元素作为占位符。

$.fn.swapWith = function(that) {
  var $this = this;
  var $that = $(that);
  
  // create temporary placeholder
  var $temp = $("<div>");
  
  // 3-step swap
  $this.before($temp);
  $that.before($this);
  $temp.before($that).remove();
        
  return $this;
}
  1. 将临时div temp 放置在 this 之前。

  2. this 移动到 that 之前。

  3. that 移动到 temp 之前。

3b) 删除 temp

然后像这样使用它:

$(selectorA).swapWith(selectorB);

演示:https://jsfiddle.net/7t1hz94y/


4
这是最佳答案,其他答案都假设元素相邻。这个答案在任何情况下都适用。 - brohr
谢谢,这个解决方案适用于所有情况,并且详细而简单地解释了,也可以标记为正确答案。我猜这个 https://dev59.com/73RB5IYBdhLWcg3wLUq3#61212946 类似,但解释较少,稍微复杂一些。 - Victor
1
@robisrob和其他读者可能会注意到,3)将其移动到temp之后也可以是3)将其移动到temp之前,因为temp在被用作锚点后将被删除。 - Victor

11

你不需要两个克隆,一个就够了。根据Paolo Bergantino的回答:

jQuery.fn.swapWith = function(to) {
    return this.each(function() {
        var copy_to = $(to).clone(true);
        $(to).replaceWith(this);
        $(this).replaceWith(copy_to);
    });
};

应该更快。传递两个元素中较小的那个也可以加速处理。


4
这种情况下,Paolo的方法和这个方法的问题在于它们无法交换具有ID的元素,因为ID必须是唯一的,所以克隆并不起作用。Bobince的解决方案在这种情况下可行。 - Ruud
另一个问题是这将从copy_to中删除事件。 - Jan Willem B

8

我之前使用过类似的技术。我用它来管理http://mybackupbox.com上的连接器列表。

// clone element1 and put the clone before element2
$('element1').clone().before('element2').end();

// replace the original element1 with element2
// leaving the element1 clone in it's place
$('element1').replaceWith('element2');

这是我认为最好的解决方案,但如果您不继续链式操作,则无需使用 end()。那么,$('element1').clone().before('element2').end().replaceWith('element2'); 怎么样? - robisrob
1
刚刚注意到clone()不会保留任何jQuery data(),除非你指定clone(true) - 如果你正在排序,这是一个重要的区别,这样你就不会丢失所有数据。 - robisrob

3

我已经编写了一个函数,可以让您将多个所选选项向上或向下移动。

$('#your_select_box').move_selected_options('down');
$('#your_select_boxt').move_selected_options('up');

依赖项:

$.fn.reverse = [].reverse;
function swapWith() (Paolo Bergantino)

首先,它会检查第一个/最后一个选定的选项是否能够向上/向下移动。 然后,它会循环遍历所有元素并调用

swapWith(element.next() 或 element.prev())

jQuery.fn.move_selected_options = function(up_or_down) {
  if(up_or_down == 'up'){
      var first_can_move_up = $("#" + this.attr('id') + ' option:selected:first').prev().size();
      if(first_can_move_up){
          $.each($("#" + this.attr('id') + ' option:selected'), function(index, option){
              $(option).swapWith($(option).prev());
          });
      }
  } else {
      var last_can_move_down = $("#" + this.attr('id') + ' option:selected:last').next().size();
      if(last_can_move_down){
        $.each($("#" + this.attr('id') + ' option:selected').reverse(), function(index, option){
            $(option).swapWith($(option).next());
        });
      }
  }
  return $(this);
}

这种写法既低效,又会影响性能。 - Blaise
这不是我们应用程序的主要功能,我只使用了少量选项。我只想要快速简单的东西...如果您能改进它,我会非常感激!那将是太棒了! - Tom Maeckelberghe

3

另一种不需要克隆的方法:

我有一个实际元素和一个名义上的元素需要交换:

            $nominal.before('<div />')
            $nb=$nominal.prev()
            $nominal.insertAfter($actual)
            $actual.insertAfter($nb)
            $nb.remove()

如果您无法确保之前始终有元素(在我的情况下),则只需要执行insert <div> beforeremove操作。


或者你可以检查.prev()的长度,如果为0,则将其.prepend().parent()中 :) 这样你就不需要创建额外的元素了。 - jave.web

3
这是我处理父元素内多个子元素上下移动的解决方案。适用于在列表框(<select multiple></select>)中移动选定的选项。 向上移动:
$(parent).find("childrenSelector").each((idx, child) => {
    $(child).insertBefore($(child).prev().not("childrenSelector"));
});

向下移动:

$($(parent).find("childrenSelector").get().reverse()).each((idx, child) => {
    $(opt).insertAfter($(child).next().not("childrenSelector"));
});

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