在DOM中重新排列HTML元素

18

我有一个元素列表:

<div class="wrapper">
    <div class="abc"></div>
    <div class="abc"></div>
    <div class="abc"></div>
</div>

我有一个由数字组成的数组,它代表了新的顺序:

var arr = [2,1,0];

我想重新定位这些abc div到父包装器内的新位置。

重要的是,这些abc div有事件和数据附加到它们上面,需要保留!

我已经想出了以下解决方案,并且似乎按预期工作:

var wrapper = $('.wrapper'), items = wrapper.children('div.abc');

var orderedItems = $.map(arr, function(value) {
    return $(items).clone(true,true).get(value);
});
wrapper.empty().html(orderedItems);

我想确认这是正确的方法。

如果可能的话,我也可以使用JavaScript解决方案。


3
就记录而言,jQuery是用JavaScript编写的框架,这意味着您拥有的是JavaScript解决方案。 - Andrue Anderson
1
如果它能正常工作,那就好了!您还可以使用CSS的flexbox和order进行重新排序:http://www.w3schools.com/cssref/css3_pr_order.asp - RobertAKARobin
6个回答

22
如果你想要一个纯 JavaScript 的解决方案(不涉及 jQuery)。
var arr = [2,1,0];
var wrapper = document.getElementsByClassName("wrapper");
var items = wrapper[0].children;
var elements = document.createDocumentFragment();

arr.forEach(function(idx) {
    elements.appendChild(items[idx].cloneNode(true));
});

wrapper[0].innerHTML = null;
wrapper[0].appendChild(elements);

我的先前回答稍加改进。示例代码: https://jsfiddle.net/jltorresm/1ukhzbg2/2/


5
请使用文档片段来批量携带项目。 - Thomas
2
你是对的Thomas,我改变了我的答案来使用文档片段。 - j2e
2
但是采用这种方式,我将失去附加到该元素的所有事件侦听器,cloneNode 不会携带任何侦听器。 - John Balvin Arias
当您将documentFragment附加到文档中时,它本身会显示为父元素吗? - oldboy

14

注意,当您添加一个已经存在于 DOM 中的元素时,该元素将被移动,而不是复制。

CodePen

let wrapper=document.querySelector(".wrapper");
let children=wrapper.children;
let newOrder=[3,2,1,0];
//it means that the first element you want it to be the four element
//The second element you want it to be the third
//the third element you want it to be the second
//the four element you want it to be the first
for(let i=0;i<newOrder.length;i++){
  for(let j=0;j<newOrder.length;j++){
    if(i==newOrder[j]){
      wrapper.appendChild(children[j]);
      break;
    }
  }
}
<div class="wrapper">
  <div>a</div>
  <div>b</div>
  <div>c</div>
  <div>d</div>
</div>


10
我知道这是一个老问题,但是谷歌把我带到了它。
在flexbox(css)中有一个名为“order”的子属性,它允许您选择显示元素的顺序。可以使用JavaScript更改此子属性并重新排列显示的元素。
https://www.w3schools.com/cssref/css3_pr_order.asp
编辑1 - 代码示例:
<style>
 .wrapper {
   display: flex;
   flex-flow: column;
 }
</style>

<div class="wrapper">
  <div class="abc">One</div>
  <div class="abc">Tow</div>
  <div class="abc">Three</div>
</div>
<button id="reorder" type="button">Reorder</button>
<button id="unreorder" type="button">Unreorder</button>

<script>
var reorderButton = document.querySelector('#reorder');
var unreorderButton = document.querySelector('#unreorder');
var abcDivs = document.querySelectorAll('.abc');
var newOrder = [2, 1, 0];

reorderButton.addEventListener('click', function() {
  abcDivs.forEach(function(element, index) {
    element.style.order = newOrder[index];
  });
});
unreorderButton.addEventListener('click', function() {
  abcDivs.forEach(function(element, index) {
    element.style.order = null;
  });
});
</script>

1
除了w3schools链接外,最好提供一个适用于问题的示例。 - Mark Stewart
1
这是一个很好的、简单的建议,适用于在您可以使用 flexbox 的情况下实现此功能。谢谢! - Milan Iliev
好建议 - alib0ng0
不错的想法。非常感谢你的小费。 - DroidOS
规范和MDN明确建议不要使用这种技巧(除非重新排序纯粹是视觉效果,没有语义):“如果您使用反向值或以其他方式重新排序项目,则应考虑是否实际上需要更改源中的逻辑顺序。规范继续警告不要使用重新排序来解决源中的问题。” - Olivier Cailloux
这个解决方案不符合无障碍标准,它破坏了制表顺序与视觉顺序,并且对于某些屏幕阅读器的使用效果并不好。 - LB Ben Johnston

8

不需要复制所有项目两次。

(这句话涉及到IT技术中的代码复制问题)
var wrapper = $('.wrapper'), 
    items = wrapper.children('.abc'),
    arr = [2,1,0];

//items.detach(); if arr doesn't reuse all items
wrapper.append( $.map(arr, function(v){ return items[v] }) );

我不确定你所说的“复制两次”是什么意思。我已经尝试了你的示例,但它并没有复制我在原帖中提到的事件(这就是为什么我使用了jQuery的克隆功能)。我有什么遗漏吗? - Toniq
请展示您使用的确切代码,并解释哪些部分不起作用。我的代码中的重点是,它创建了一个按正确顺序排列的实际DOM节点的中间数组,并将它们附加到包装器上。通过将DOM节点添加到(新的)父级,它首先从其所在的节点中删除,然后添加到新节点中。没有克隆节点,没有更改任何引用/事件/属性/任何内容,只有DOM树中的位置。 - Thomas
我所说的“复制两次”是指:首先,您需要创建当前节点的克隆,然后使用$.html()将其转换为字符串表示形式,并再次解析此字符串表示形式(其中节点基本上被克隆)。 - Thomas

4

这个解决方案对我来说更好,我们建立了一个元素数组([...wrapper.children]),然后根据模型([5,4,3,2,1,0])使用 .sort 方法进行排序,然后使用 appendChild。由于元素与原始元素相同,事件监听器仍然正常工作。以下是代码:

//this is just to add event listeners
[...document.getElementsByClassName("abc")].forEach(e =>
  e.addEventListener("click", ev => console.log(ev.target.innerText))
);

var wrapper = document.getElementsByClassName("wrapper")[0];
var items = [...wrapper.children];

const newOrder = [5,4,3,2,1,0]

items.sort((a, b)=>newOrder.indexOf(items.indexOf(a)) - newOrder.indexOf(items.indexOf(b)));

items.forEach(it=>wrapper.appendChild(it));
<div class="wrapper">
  <div class="abc">0</div>
  <div class="abc">1</div>
  <div class="abc">2</div>
  <div class="abc">3</div>
  <div class="abc">4</div>
  <div class="abc">5</div>
</div>

如您所见,如果点击0、1等数字,事件监听器便会起作用。


0

在阅读了其他人的解决方案后,我创建了一个简单的代码片段来反转被包装在单个容器元素中的

  • 等元素。

    // Select all divs which need to be reordered
    const items = document.querySelectorAll("#plan-carousel .plans-carousel .column-holder")
    // Create an array of the divs and reverse them
    const reverseItems = Array.from(items).reverse()
    // Select the parent or wrapper element
    const wrapper = document.querySelector("#plan-carousel .plans-carousel")
    // Iterate over the reversed array and append each one to the parent element
    reverseItems.forEach(item => wrapper.appendChild(item))
    

    参考资料:

    1. 根据MDN的appendChild()文章:如果给定的子节点是文档中现有节点的引用,则appendChild()将其从当前位置移动到新位置。

    换句话说,由于这些项已经存在,当我们添加它们时,它们只是被移动而不是复制。


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