原生JS拖拽脚本改写遇insertBefore错误:目标节点非当前节点子元素
问题
将jQuery拖拽脚本改写为原生JavaScript时,使用insertBefore方法触发错误:NotFoundError: Node.insertBefore: Child to insert before is not a child of this node.,该错误仅在尝试将列表下方元素移至顶部时出现。
原代码
JavaScript
let items = document.querySelectorAll("#items-list > li"); items.forEach((item) => { item.setAttribute("draggable", true); item.addEventListener("dragstart", dragStart); item.addEventListener("drop", dropped); item.addEventListener("dragenter", cancelDefault); item.addEventListener("dragover", cancelDefault); }); const getNodeIndex = elm => [...elm.parentNode.children].indexOf(elm) function dragStart(e) { const index = getNodeIndex(e.target); e.dataTransfer.setData("text/plain", index); } function dropped(e) { cancelDefault(e); // 获取新旧索引 let oldIndex = e.dataTransfer.getData("text/plain"); let target = e.target; let newIndex = getNodeIndex(e.target); let droppedElement = e.currentTarget.parentNode.children[oldIndex]; droppedElement.remove(); // 将元素插入新位置 if (newIndex < oldIndex) { // --- 问题代码行 --- target.insertBefore(droppedElement, e.currentTarget.parentNode); } else { target.after(droppedElement); } } function cancelDefault(e) { e.preventDefault(); e.stopPropagation(); return false; }
HTML
<div class="container"> <ul id="items-list" class="moveable"> <li>One</li> <li>Two</li> <li>Three</li> <li>Four</li> </ul> </div>
解决方案
错误根源是insertBefore的调用逻辑错误:你试图在列表项target下插入元素,同时把父节点ul作为“插入位置的前置节点”,但ul并不是target的子节点,因此触发报错。
此外还有两个潜在问题需要修复:
e.target可能指向列表项内部的文本节点,导致后续索引计算错误- 移除元素后,原索引对应的元素会发生变化,直接通过
oldIndex获取元素会失效
修正后的JavaScript代码
let items = document.querySelectorAll("#items-list > li"); const listContainer = document.getElementById("items-list"); items.forEach((item) => { item.setAttribute("draggable", true); item.addEventListener("dragstart", dragStart); item.addEventListener("drop", dropped); item.addEventListener("dragenter", cancelDefault); item.addEventListener("dragover", cancelDefault); }); const getNodeIndex = elm => [...elm.parentNode.children].indexOf(elm) function dragStart(e) { // 确保获取的是li元素,而非内部文本节点 const targetEl = e.target.closest("li"); const index = getNodeIndex(targetEl); e.dataTransfer.setData("text/plain", index); } function dropped(e) { cancelDefault(e); // 确保获取的是li元素 const targetEl = e.target.closest("li"); let oldIndex = parseInt(e.dataTransfer.getData("text/plain")); let newIndex = getNodeIndex(targetEl); // 先保存要移动的元素,避免移除后索引失效 let droppedElement = listContainer.children[oldIndex]; droppedElement.remove(); // 插入到正确位置 if (newIndex < oldIndex) { // 由父容器调用insertBefore,将元素插到目标项之前 listContainer.insertBefore(droppedElement, targetEl); } else { targetEl.after(droppedElement); } } function cancelDefault(e) { e.preventDefault(); e.stopPropagation(); return false; }
关键修改说明
- 提前获取父容器
listContainer,避免重复DOM查询 - 使用
closest("li")确保始终获取到列表项元素,规避文本节点导致的错误 - 修正
insertBefore调用逻辑:由父容器执行插入操作,第二个参数为目标列表项 - 先保存待移动元素再执行移除,防止索引失效问题
内容的提问来源于stack exchange,提问作者Max Pattern
相关产品推荐
相关产品推荐

