为何Todo列表拖拽交换函数对新增元素无效?
问题排查与修复:新增Todo项拖拽排序undefined错误
问题根源
listItems数组类型不统一
初始化时listItems存储的是<li>DOM元素,但新增Todo时,执行了listItems.push(todoText.textContent),把字符串存入数组。后续swapItems中尝试对字符串调用querySelector,直接抛出undefined错误。新增元素未绑定拖拽事件
addEventListeners()仅在页面初始化时执行一次,新增的<li>和可拖拽元素没有绑定dragstart、drop等事件,拖拽逻辑完全不生效。data-index未正确递增
变量index2初始值为6,但新增Todo后没有执行index2++,导致所有新增项的data-index都是6,拖拽时索引计算混乱。DOM元素类名设置错误
使用setAttribute('className', 'draggable')是错误的,DOM元素的类名属性是class,而非className(className是JS对象属性,对应DOM的class属性),正确做法是用classList.add()。
修复步骤与代码修正
1. 修正listItems存储逻辑
在newTodo函数中,将<li>元素而非文本内容推入listItems:
// 替换原错误代码:listItems.push(todoText.textContent); listItems.push(todoLi);
2. 给新增元素绑定拖拽事件
单独封装绑定单个元素事件的函数,避免重复绑定已有元素的事件:
// 新增函数:绑定单个可拖拽项的事件 function bindDragEvents(draggableItem, listItem) { draggableItem.addEventListener('dragstart', dragStart); listItem.addEventListener('dragover', dragOver); listItem.addEventListener('drop', dragDrop); listItem.addEventListener('dragenter', dragEnter); listItem.addEventListener('dragleave', dragLeave); } // 在createList的forEach中替换原绑定逻辑: bindDragEvents(listItem.querySelector('.draggable'), listItem); // 在newTodo函数末尾,给新增元素绑定事件: bindDragEvents(todo, todoLi);
3. 递增data-index
在newTodo函数开头执行index2++,确保每个新增项的索引唯一:
function newTodo(value) { index2++; // 新增这一行 // ... 原代码 }
4. 修正类名设置方式
替换所有setAttribute('className', ...)为classList.add():
// createList函数中: // listItem.setAttribute('className', 'draggable'); listItem.classList.add('draggable'); // newTodo函数中: // todoLi.setAttribute('className', 'draggable'); todoLi.classList.add('draggable');
完整修正后的关键代码片段
// 新增绑定单个元素事件的函数 function bindDragEvents(draggableItem, listItem) { draggableItem.addEventListener('dragstart', dragStart); listItem.addEventListener('dragover', dragOver); listItem.addEventListener('drop', dragDrop); listItem.addEventListener('dragenter', dragEnter); listItem.addEventListener('dragleave', dragLeave); } function createList() { [...TodoList].forEach((todo, index) => { const listItem = document.createElement('li'); listItem.setAttribute('data-index', index + 1); listItem.classList.add('draggable'); // 修正类名设置 listItem.innerHTML = ` <label for="showRatings${index + 1}" class="circle${index + 1} check"> <input type="checkbox" class="checkbox input" name="showRatings${index + 1}" id="showRatings${index + 1}"> </label> <span class="number">${index + 1+"-"}</span> <div class="draggable" draggable="true"> <p class="todo-name" >${todo}</p> </div>`; listItems.push(listItem); draggableElement.appendChild(listItem); // 绑定事件 bindDragEvents(listItem.querySelector('.draggable'), listItem); }); } function newTodo(value) { index2++; // 递增索引 const todo = document.createElement('div'); const todoText = document.createElement('p'); todoText.classList.add('todo-name') const todoCheckbox = document.createElement('input'); const todoCheckBoxLabel = document.createElement('label'); todo.setAttribute('draggable', true); todo.classList.add('draggable') const numberSpan = document.createElement('span'); numberSpan.classList.add('number') const todoLi = document.createElement('li'); todoLi.classList.add('draggable', 'list-item', 'not-crossed'); // 修正类名设置 const toDosContainer = document.querySelector('.draggable-list') const Container = document.querySelector('.container-todo') todoCross = document.createElement('span'); todoCross.classList.add('cross') todoLi.setAttribute('data-index', index2); numberSpan.textContent = "-"; todoText.textContent = value; todoCheckbox.type = "checkbox"; todoCheckbox.name = `showRatings${index2}`; // 修正name避免重复 todoCheckBoxLabel.htmlFor = `showRatings${index2}`; // 修正for避免重复 todoCheckBoxLabel.addEventListener('click', function(e) { if (todoCheckbox.checked) { todoCheckbox.checked = false; todoText.style.textDecoration = 'none'; todoCheckBoxLabel.classList.remove('active'); todoLi.classList.add('not-crossed') todoLi.classList.remove('thick-crossed') } else { todoCheckbox.checked = true; todoText.style.textDecoration = "line-through"; todoCheckBoxLabel.classList.add('active'); todoLi.classList.remove('not-crossed') todoLi.classList.add('thick-crossed') } }); TodoList.push(todoText.textContent); listItems.push(todoLi); // 存储li元素而非文本 console.log('lista', listItems); todoCross.textContent = 'X'; todoCross.addEventListener('click', function(e) { // 移除时同步从listItems中删除 const removeIndex = listItems.indexOf(todoLi); if (removeIndex !== -1) { listItems.splice(removeIndex, 1); } e.target.parentElement.parentElement.remove(); // 修正层级,因为todoLi是父元素 }); Container.classList.add('todos-container'); todo.classList.add('todo'); todoCheckbox.classList.add('checkbox') todoCheckBoxLabel.classList.add('circle', 'chk'); todo.appendChild(todoCheckbox); todo.appendChild(todoCheckBoxLabel); todo.appendChild(numberSpan) todo.appendChild(todoText); todo.appendChild(todoCross); todoLi.appendChild(todo); toDosContainer.appendChild(todoLi); // 给新增元素绑定拖拽事件 bindDragEvents(todo, todoLi); }
额外优化点
- 删除Todo时同步从
listItems数组中移除对应元素,避免数组和DOM不一致 - 修正新增项的checkbox的
name和for属性,避免重复ID/name导致的交互问题 - 可以使用事件委托替代逐个绑定事件,提升性能,比如给父元素
draggable-list绑定dragover、drop等事件,无需给每个li单独绑定
内容的提问来源于stack exchange,提问作者Abraham Martinez
相关产品推荐
相关产品推荐

