纯JavaScript方法将内容包装在div中

99

我想使用JavaScript将所有节点包裹在 #slidesContainer div 中。我知道这在jQuery中很容易实现,但我想知道如何使用纯JS实现。

以下是代码:

<div id="slidesContainer">
    <div class="slide">slide 1</div>
    <div class="slide">slide 2</div>
    <div class="slide">slide 3</div>
    <div class="slide">slide 4</div>
</div>

我希望将具有class为"slide"的div元素作为整体,放置在另一个拥有id="slideInner"的div元素内。

13个回答

98
如果您的“slide”始终在slidesContainer中,则可以这样做。
org_html = document.getElementById("slidesContainer").innerHTML;
new_html = "<div id='slidesInner'>" + org_html + "</div>";
document.getElementById("slidesContainer").innerHTML = new_html;

5
@Squadrons: 你也在使用jQuery吗?如果是的话,你真的不应该这样做。(即使你没有,你也会不必要地破坏和重建DOM的一部分。) - user113716
1
我没有使用jQuery。在转向框架之前,我正在努力熟悉JS。但是我正在尝试此线程中的所有答案。您推荐aroth的答案吗?如果是这样,为什么不选择这个呢? - Squadrons
1
我的答案比在节点级别上操作要慢一些,但是除非你有很多节点(幻灯片),否则差异应该是无法察觉的。如果您正在使用jquery/或已将任何事件连接到幻灯片,则需要重新扫描dom。但是,这种方法在大多数浏览器中都可以工作,而不需要处理getElementsByClassName不兼容性。因此,对于简单的功能(和不太复杂的DOM),这可能更好...重要的是要说,如果您正在动态生成幻灯片,则使用innerHtml比逐个构建DOM树节点更快。 - Michal
27
请注意,替换innerHTML将会移除所有包含节点的事件。 - user2467065
6
这将清除元素上的任何事件处理程序等内容,而且速度相对较慢。我更喜欢@user113716的答案。 - Ansikt
显示剩余4条评论

51

如何“包装内容”和“保留绑定事件”?

// element that will be wrapped
var el = document.querySelector('div.wrap_me');

// create wrapper container
var wrapper = document.createElement('div');

// insert wrapper before el in the DOM tree
el.parentNode.insertBefore(wrapper, el);

// move el into wrapper
wrapper.appendChild(el);

或者

function wrap(el, wrapper) {
    el.parentNode.insertBefore(wrapper, el);
    wrapper.appendChild(el);
}

// call function
// wrapping element "wrap_me" into a new element "wrapper"
wrap(document.querySelector('div.wrap_me'), document.createElement('div'));

引用

https://plainjs.com/javascript/manipulation/wrap-an-html-structure-around-an-element-28


1
这基本上是与2013年用户1767210的答案相同的答案:https://dev59.com/gmw15IYBdhLWcg3wFHzf#18453767,但可能更清晰。 - redfox05
@redfox05 当然,因为这是常见的模式-请参见参考链接。 - Bruno

50

像BosWorth99一样,我也喜欢直接操作dom元素,这有助于维护所有节点的属性。但是,我想保持元素在dom中的位置,而不仅仅是将其附加到末尾,以防有兄弟节点。这是我所做的。

var wrap = function (toWrap, wrapper) {
    wrapper = wrapper || document.createElement('div');
    toWrap.parentNode.insertBefore(wrapper, toWrap);
    return wrapper.appendChild(toWrap);
};

2
我确信这是一个不好的问题,因为我并不像熟悉CSS那样熟悉Javascript/jQuery。但为什么你要将它设置为一个变量而不是分配为一个函数呢?例如:function wrap(toWrap, wrapper) { - darcher
我通常使用函数表达式而不是声明来保持一致的编码风格并简化提升行为。https://javascriptweblog.wordpress.com/2010/07/06/function-declarations-vs-function-expressions/ - Phillip Chan
2
这并没有回答问题。OP想要包装所有子元素而不是单个元素。 - Web_Designer
13
如果 toWrap 不是最后一个子元素,则 wrapper 就会成为错误的子元素(它最终会成为最后一个子元素)。更好的方法是使用 insertBefore(),例如将 toWrap.parentNode.appendChild(wrapper); 替换为 toWrap.parentNode.insertBefore(wrapper, toWrap); - MoonLite

16

如果你想在没有jQuery的情况下完成通常需要使用jQuery来完成的任务,一个好的技巧是查看jQuery源代码。那么他们都做了些什么?他们获取所有子元素,将它们追加到新节点中,然后将该节点追加到父节点内部。

以下是一个简单的方法来完成这个操作:

const wrapAll = (target, wrapper = document.createElement('div')) => {
  ;[ ...target.childNodes ].forEach(child => wrapper.appendChild(child))
  target.appendChild(wrapper)
  return wrapper
}

这是如何使用它的方法:

// wraps everything in a div named 'wrapper'
const wrapper = wrapAll(document.body) 

// wraps all the children of #some-list in a new ul tag
const newList = wrapAll(document.getElementById('some-list'), document.createElement('ul'))

我喜欢第二个参数是可选的。你可以提供一个元素,或者它会为你创建一个 div。这非常好用,谢谢! - Justin Breen
1
现在您可以使用 parentNode.append 代替 appendChild。例如:wrapper.append(...target.childNodes) - Azmisov
在第一个代码片段的第二行的第一个字母后面的分号是用来干什么的? - user19690494
1
@CodemasterUnited,简短的版本是:你不需要它。长版本是:你可以在JavaScript中不使用分号编写代码,但有几个例外情况,其中解释器可能会感到困惑。其中之一是如果一行以数组文字开头,则可以将其视为从前一行索引元素。这在这里不是问题,因为这是函数的第一行,但当时我正在使用一个强制执行该规则的linter。 - Ansikt

16
如果你修补document.getElementsByClassName以适应IE, 你可以这样做:
var addedToDocument = false;
var wrapper = document.createElement("div");
wrapper.id = "slideInner";
var nodesToWrap = document.getElementsByClassName("slide");
for (var index = 0; index < nodesToWrap.length; index++) {
    var node = nodesToWrap[index];
    if (! addedToDocument) {
        node.parentNode.insertBefore(wrapper, node);
        addedToDocument = true;
    }
    node.parentNode.removeChild(node);
    wrapper.appendChild(node);
}

示例: http://jsfiddle.net/GkEVm/2/


@aroth:没错,现在可以工作了。如果您只是在循环外添加包装器,那么您可以简化一下。此外,请记住,DOM节点一次只能存在于一个位置,因此您确实不需要执行removeChild然后执行appendChild。只需执行appendChild即可,它将从当前位置中删除该节点。http://jsfiddle.net/GkEVm/3/ ;o) - user113716
@patrick - 更加简洁,但是如果getElementsByclassName没有匹配到任何元素,修改后的版本不会出现错误吗? - aroth
@aroth:非常好的观点。我的方法需要添加 if( nodesToWrap[0] ) - user113716
有人可以解释一下 var addedToDocument = false 吗? - Squadrons
1
@Squadrons - addedToDocument 变量只是一个标志,用于指示包装元素是否已添加到 DOM 中。其原因是:1)包装 div 的插入位置取决于匹配的节点;2)可能没有匹配的节点,在这种情况下,包装器根本不应该添加到 DOM 中;3)当有多个节点匹配时,包装器应该只在第一个匹配项前添加到 DOM 中一次。该标志仅确保满足所有这些条件。 - aroth

14

我喜欢直接操作DOM元素 - createElement、appendChild、removeChild等,而不是像使用element.innerHTML那样注入字符串。那种策略虽然可行,但我认为本地浏览器方法更加直接。此外,它们返回一个新节点的值,免去了你再次调用getElementById的无谓麻烦。

这非常简单,并且需要连接到某种事件才能发挥作用。

wrap();
function wrap() {
    var newDiv = document.createElement('div');
    newDiv.setAttribute("id", "slideInner");
    document.getElementById('wrapper').appendChild(newDiv);
    newDiv.appendChild(document.getElementById('slides'));
}

这里提供的 jsFiddle 可能有助于您理解使用原生 JavaScript 解决此问题。


为什么需要将它附加?我尝试通过将其附加到名为prepareSlideshow()的函数并在加载时运行它,但似乎无法更改DOM以使其显示所需节点。 - Squadrons
附加 - 这样你就可以控制它,就像你调用的onLoad事件一样。我的意思是 - 你也可以直接调用它,我只是假设幻灯片没有被包装在一个状态中,并且你需要在运行时更改元素列表。只需在任何函数上运行它,只要你的元素ID名称匹配,它就应该工作... - Bosworth99

13

简单地包装一个 div,而不需要父元素:

<div id="original">ORIGINAL</div>

<script>

document.getElementById('original').outerHTML
=
'<div id="wrap">'+
document.getElementById('original').outerHTML
+'</div>'

</script>

工作示例:https://jsfiddle.net/0v5eLo29/

更实用的方法:

const origEle = document.getElementById('original');
origEle.outerHTML = '<div id="wrap">' + origEle.outerHTML + '</div>';

或仅使用节点:

let original = document.getElementById('original');
let wrapper = document.createElement('div');
wrapper.classList.add('wrapper');
wrapper.append(original.cloneNode(true));

original.replaceWith(wrapper);

工作示例:https://jsfiddle.net/wfhqak2t/


4
一种简单的方法是:
let el = document.getElementById('slidesContainer');
el.innerHTML = `<div id='slideInner'>${el.innerHTML}</div>`;

它根本不会换行,而是将其插入其中。换行是从外部进行的。 - vsync

1
注意:下面回答的是问题的标题,但不特指 OP 的要求(该要求已超过十年)。
使用 range API 可以轻松地进行包装,方法是创建一个 Range,该 Range 选择 仅需要包装的节点,然后使用 surroundContents API 进行包装。
下面的代码将第一个(文本)节点用 <mark> 元素包装,并将最后一个节点用 <u> 元素包装:

const wrapNode = (nodeToWrap, wrapWith) => {
  const range = document.createRange();
  range.selectNode(nodeToWrap);
  range.surroundContents(wrapWith);
}

wrapNode(document.querySelector('p').firstChild, document.createElement('mark'))
wrapNode(document.querySelector('p').lastChild, document.createElement('u'))
<p>
  first node
  <span>second node</span>
  third node
</p>


0
将所有子元素包裹在一个元素中

const wrapper = document.createElement("div");
wrapper.classList.add("wrapper");
document.querySelector('#slidesContainer').appendChild(wrapper);

// select all the elements we want to wrap
const slides = document.querySelectorAll(".slide");

// loop over the elements
slides.forEach((item, index) => {

  // add the element to the wrapper
  wrapper.appendChild(item);
});
.wrapper {
  border: 1px solid black;
}
<div id="slidesContainer">
  <div class="slide">slide 1</div>
  <div class="slide">slide 2</div>
  <div class="slide">slide 3</div>
  <div class="slide">slide 4</div>
</div>

逐个包装

使用querySelectorAll、forEach、createElement和before方法

// select all the elements we want to wrap
const slides = document.querySelectorAll(".slide");

// loop over the elements
slides.forEach((item, index) => {
  const wrapper = document.createElement("div");
  wrapper.classList.add("wrapper");
  item.before(wrapper);

  // add the element to the wrapper
  wrapper.appendChild(item);
});
.wrapper {
  border: 1px solid black;
}
<div id="slidesContainer">
  <div class="slide">slide 1</div>
  <div class="slide">slide 2</div>
  <div class="slide">slide 3</div>
  <div class="slide">slide 4</div>
</div>

分组包装

如果你想要将多个物品用一个包装纸包起来,你可以使用一个循环和取模运算来确定是否需要新的包装元素。

// convert html collection into an array
const slides = [...document.querySelectorAll(".slide")];

slides.reduce((wrapper, item, index) => {
  // Every 2 so if we are at a start create a new wrapper element
  // And place it before the html element we want to move
  if (index % 2 === 0) {
    wrapper = document.createElement("div");
    wrapper.classList.add("wrapper");
    item.before(wrapper);
  }
  
  // add the element to the wrapper
  wrapper.appendChild(item);
  
  // keep track of the wrapper
  return wrapper;
}, null);
.wrapper {
  border: 1px solid black;
}
<div id="slidesContainer">
    <div class="slide">slide 1a</div>
    <div class="slide">slide 1b</div>
    <div class="slide">slide 2a</div>
    <div class="slide">slide 2b</div>
    <div class="slide">slide 3a</div>
    <div class="slide">slide 3b</div>
    <div class="slide">slide 4a</div>
    <div class="slide">slide 4b</div>
</div>


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