使用insertAdjacentHTML生成的动态列表拖拽功能失效问题排查
拖拽功能在insertAdjacentHTML生成的列表项上失效的原因及解决方法
我用insertAdjacentHTML动态生成活动列表项(代码如下),希望实现拖拽排序,但功能失效。之前用createElement()+appendChild()创建元素时,拖拽完全正常。请问这是insertAdjacentHTML的特定问题吗?
生成列表的代码:
const renderList = (activities) => { const display = document.getElementById('task-list-display'); activities.forEach((activity) => { console.log(activity); display.insertAdjacentHTML('beforeend', ` <li class="task-item draggable" draggable="true"> <div class="chk-descr"> <input data-a1="${activity.index}" type="checkbox" name="completed"/> <p data-b1="${activity.index}" class="description" contenteditable="true">${activity.description}</p> </div> </li> `); }); };
拖拽相关代码:
let activities = []; const dragstart = (element) => { element.classList.add('skateover'); }; const dragover = (element, e) => { e.preventDefault(); element.classList.add('dragover'); }; const dragleave = (element) => { element.classList.remove('dragover'); }; const drop = (element) => { const skateover = document.querySelector('.skateover'); element.before(skateover); repopulateList(); element.classList.remove('dragover'); }; const dragend = (element) => { element.classList.remove('skateover'); }; const repopulateList = () => { const listItems = document.querySelectorAll('.draggable'); emptyList(); let i = 0; listItems.forEach((listItem) => { listItem.setAttribute('activity', i); i += 1; const description = listItem.getElementsByClassName('description')[0].textContent; const completed = listItem.getElementsByClassName('completed')[0].checked; const index = listItem.getAttribute('activity'); inputActivity(description, completed, index); }); }; const inputActivity = (description, completed, index) => { activities.push({ description, completed, index: parseInt(index, 10) }); };
HTML结构:
<ul id="task-list-display"></ul>
原因分析
核心问题是事件绑定的时机和方式:
- 你之前用
createElement()时,应该是在创建元素后直接通过addEventListener给每个列表项绑定了拖拽事件,所以新元素都有对应的事件监听。 - 但
insertAdjacentHTML是直接把字符串解析成DOM节点插入文档,生成的新元素不会自动绑定任何事件——你定义的dragstart、dragover等函数只是存在,但没有被关联到这些新生成的.draggable元素上。
另外,repopulateList里的emptyList()如果是清空列表再重渲染,重渲染后的元素依然没有绑定事件,也会导致拖拽失效。
解决方法
1. 事件委托(推荐)
不需要给每个列表项单独绑事件,把事件监听绑定到父容器#task-list-display上,利用事件冒泡处理所有子元素的拖拽事件。不管后续用什么方式生成列表项,事件都会被父容器捕获。
修改后的代码:
document.addEventListener('DOMContentLoaded', () => { const listContainer = document.getElementById('task-list-display'); // 处理dragstart listContainer.addEventListener('dragstart', (e) => { const draggableItem = e.target.closest('.draggable'); if (draggableItem) { draggableItem.classList.add('skateover'); } }); // 处理dragover listContainer.addEventListener('dragover', (e) => { e.preventDefault(); const draggableItem = e.target.closest('.draggable'); if (draggableItem) { draggableItem.classList.add('dragover'); } }); // 处理dragleave listContainer.addEventListener('dragleave', (e) => { const draggableItem = e.target.closest('.draggable'); if (draggableItem) { draggableItem.classList.remove('dragover'); } }); // 处理drop listContainer.addEventListener('drop', (e) => { e.preventDefault(); const dropTarget = e.target.closest('.draggable'); const skateoverItem = document.querySelector('.skateover'); if (dropTarget && skateoverItem && dropTarget !== skateoverItem) { dropTarget.before(skateoverItem); repopulateList(); dropTarget.classList.remove('dragover'); } }); // 处理dragend listContainer.addEventListener('dragend', (e) => { const draggableItem = e.target.closest('.draggable'); if (draggableItem) { draggableItem.classList.remove('skateover'); } }); });
2. 插入元素后手动绑定事件
如果坚持给每个元素单独绑事件,可以在insertAdjacentHTML插入后,找到刚生成的元素并绑定事件:
修改renderList函数:
const renderList = (activities) => { const display = document.getElementById('task-list-display'); activities.forEach((activity) => { // 插入HTML display.insertAdjacentHTML('beforeend', ` <li class="task-item draggable" draggable="true"> <div class="chk-descr"> <input data-a1="${activity.index}" type="checkbox" name="completed"/> <p data-b1="${activity.index}" class="description" contenteditable="true">${activity.description}</p> </div> </li> `); // 获取刚插入的最后一个元素 const lastItem = display.lastElementChild; // 绑定拖拽事件 lastItem.addEventListener('dragstart', () => dragstart(lastItem)); lastItem.addEventListener('dragover', (e) => dragover(lastItem, e)); lastItem.addEventListener('dragleave', () => dragleave(lastItem)); lastItem.addEventListener('drop', () => drop(lastItem)); lastItem.addEventListener('dragend', () => dragend(lastItem)); }); };
这种方式需要每次插入后手动绑定,适合列表更新不频繁的场景,相比事件委托繁琐度更高。
内容的提问来源于stack exchange,提问作者German
相关产品推荐
相关产品推荐

