JavaScript拖拽模板优化:阻止左侧区块移除并支持多次拖拽
解决方案
问题根源是你直接将左侧的原始DOM元素移动到了右侧容器,导致左侧元素被移除。要实现复制拖拽(而非移动),核心是在拖拽完成时创建原元素的克隆版本,再将克隆添加到右侧容器。
修改核心逻辑
1. 规范拖拽数据传递
在dragstart事件中,通过dataTransfer传递区块ID,让右侧能准确获取对应区块的数据,避免依赖DOM元素属性:
draggable.addEventListener('dragstart', (e) => { draggable.classList.add('dragging'); // 传递区块ID到拖拽数据中 e.dataTransfer.setData('text/plain', draggable.getAttribute('data-block')); });
2. 拆分拖拽事件职责
按照拖拽API标准,dragover仅负责阻止默认行为以触发drop,实际添加元素的逻辑放到drop事件中:
// 替换原dragover事件监听 document.right.addEventListener('dragover', (e) => { e.preventDefault(); // 必须阻止默认行为才能触发drop }); // 添加drop事件监听 document.right.addEventListener('drop', (e) => { e.preventDefault(); const blockId = e.dataTransfer.getData('text/plain'); const targetBlock = blocksData.find(block => block.id == blockId); if (!targetBlock) return; // 创建新的区块元素(而非移动原元素) const newBlock = document.createElement('div'); newBlock.className = 'block'; newBlock.setAttribute('data-block', targetBlock.id); newBlock.innerHTML = targetBlock.htmlContent; // 确定插入位置 const afterElement = getDragAfterElement(document.right, e.clientY); if (afterElement) { document.right.insertBefore(newBlock, afterElement); } else { document.right.appendChild(newBlock); } });
3. 移除错误的DOM移动逻辑
删除原dragover事件中appendChild和insertBefore操作原DOM元素的代码,不再移动左侧的原始区块。
完整修改后的JavaScript代码
const blocksData = [{ id: 1, label: 'Test Content', htmlContent: '<h1>Test Content</h1>' }, { id: 2, label: 'Test Content', htmlContent: '<h2>Test Content</h2>' }, { id: 3, label: 'Test Content', htmlContent: '<h3>Test Content</h3>' }, { id: 4, label: 'Test Content', htmlContent: '<button>Test Content</button>' }, ]; const getDragAfterElement = (container, y) => { const draggableElements = [...container.querySelectorAll('.block:not(.dragging)')] return draggableElements.reduce((closest, child) => { const box = child.getBoundingClientRect() const offset = y - box.top - box.height / 2 if (offset < 0 && offset > closest.offset) { return { offset: offset, element: child } } else { return closest } }, { offset: Number.NEGATIVE_INFINITY }).element } const initiateHandler = () => { const left = document.getElementsByClassName("left")[0]; const handle = document.getElementById("handle"); handle.addEventListener("mousedown", (e) => { let ismdwn = 1 const mV = (event) => { if (ismdwn === 1) { left.style.flexBasis = event.clientX + "px" } else { end() } } const end = () => { ismdwn = 0 document.body.removeEventListener('mousemove', mV) document.body.removeEventListener('mouseup', end) } document.body.addEventListener('mousemove', mV) document.body.addEventListener('mouseup', end) }); } const onReady = () => { initiateHandler(); const drggableBlocksHolder = document.getElementById("blocks"); // 简化区块渲染逻辑 blocksData.forEach((block) => { drggableBlocksHolder.innerHTML += `<div draggable="true" class="block" data-block="${block.id}">${block.htmlContent}</div>`; }); const rightContainer = document.getElementsByClassName("right")[0]; const draggables = document.querySelectorAll('#blocks .block'); draggables.forEach(draggable => { draggable.addEventListener('dragstart', (e) => { draggable.classList.add('dragging'); e.dataTransfer.setData('text/plain', draggable.getAttribute('data-block')); }); draggable.addEventListener('dragend', () => { draggable.classList.remove('dragging'); }); }); rightContainer.addEventListener('dragover', (e) => { e.preventDefault(); }); rightContainer.addEventListener('drop', (e) => { e.preventDefault(); const blockId = e.dataTransfer.getData('text/plain'); const targetBlock = blocksData.find(block => block.id == blockId); if (!targetBlock) return; const newBlock = document.createElement('div'); newBlock.className = 'block'; newBlock.setAttribute('data-block', targetBlock.id); newBlock.innerHTML = targetBlock.htmlContent; const afterElement = getDragAfterElement(rightContainer, e.clientY); if (afterElement) { rightContainer.insertBefore(newBlock, afterElement); } else { rightContainer.appendChild(newBlock); } }); }; (function() { onReady() })();
额外优化
- 将全局变量
ismdwn改为局部变量,避免全局污染 - 简化区块渲染的冗余判断逻辑
- 修正了
end函数中事件监听移除的错误
修改后,左侧区块不会被移除,每次拖拽都会在右侧生成新的副本,支持多次重复拖拽。
内容的提问来源于stack exchange,提问作者Cullen
相关产品推荐
相关产品推荐

