ClassList 切换仅适用于某些元素

ClassList toggle works only for some elements

我是这个领域的绝对初学者。我正在做的是一个小的待办事项列表页面。 我被困在下面的 'button events' 部分。

如代码所示,我想将 'completed' 的 class 切换为单击按钮的祖先 div。但是,它似乎只适用于某些元素。 意思是,如果 'finishBtn' 变量的节点列表具有奇数长度,则 class 列表仅切换偶数索引,反之亦然。

例如,如果 nodelist.length = 3,则 class 列表仅切换 nodelist[0] 和节点列表[2].

(但是,对于 removeBtn 变量,它工作得很好)

非常感谢您的宝贵时间。我真的很感激你的每一个回复。 因为我被困在这个该死的东西里好几个小时了。

    addBtn.addEventListener('click', (e)=> {
        e.preventDefault();
        if(input.value === ''){
            return;
        }

        // adding elements to the body;

        const eachTodo = document.createElement('div');
        eachTodo.classList.add('eachTodo');
        const textName = document.createElement('p');
        textName.textContent = input.value;
        const btns = document.createElement('div');
        btns.classList.add('btns');
        const finish = document.createElement('button');
        finish.classList.add('finish');
        finish.textContent = 'Finish';
        const remove = document.createElement('button');
        remove.classList.add('remove');
        remove.textContent = 'Remove';
        btns.append(finish, remove);
        eachTodo.append(textName, btns);
        plansDiv.append(eachTodo);

        input.value = '';

        //button Events
        const finishBtn = document.querySelectorAll('.finish');
        const removeBtn = document.querySelectorAll('.remove');
        finishBtn.forEach(btn => {
            btn.addEventListener('click', ()=>{
                btn.parentElement.parentElement.classList.toggle('completed');
            })
        })

        removeBtn.forEach(btn => {
            btn.addEventListener('click', ()=>{
                btn.parentElement.parentElement.remove();
            })
        })
    })

这是我的CSS部分

.completed p {
text-decoration: line-through;
opacity: 0.8;
}

就目前而言,您正在查询每个 add 上的所有按钮并向它们添加新的侦听器,所有这些都会导致每个按钮上的重复侦听器按顺序触发。

附加了偶数个侦听器的元素会将 class 切换偶数次,并将 return 切换为初始值。

"on" toggle-> "off" toggle-> "on"

具有奇数个附加侦听器的元素切换 class 奇数次并在最后显示正确。

"on" toggle-> "off" toggle-> "on" toggle-> "off"

(它适用于 Remove,因为第一个侦听器删除了元素,而后续的 Removes 不会改变它。)

只为每个按钮添加一次监听器

您可以通过简单地将侦听器直接添加到新创建的按钮来避免这种情况。您已经在脚本中引用了每个 button 及其父元素 (eachTodo),因此您可以直接将侦听器添加到它们并直接引用父元素。

finish.addEventListener('click', () => {
  eachTodo.classList.toggle('completed');
});

remove.addEventListener('click', () => {
  eachTodo.remove();
});

const addBtn = document.getElementById('addBtn');
const plansDiv = document.getElementById('plansDiv');
const input = document.getElementById('input');

addBtn.addEventListener('click', (e) => {
  e.preventDefault();
  if (input.value === '') {
    return;
  }

  // adding elements to the body;

  const eachTodo = document.createElement('div');
  eachTodo.classList.add('eachTodo');
  const textName = document.createElement('p');
  textName.textContent = input.value;
  const btns = document.createElement('div');
  btns.classList.add('btns');
  const finish = document.createElement('button');
  finish.classList.add('finish');
  finish.textContent = 'Finish';
  const remove = document.createElement('button');
  remove.classList.add('remove');
  remove.textContent = 'Remove';
  btns.append(finish, remove);
  eachTodo.append(textName, btns);
  plansDiv.append(eachTodo);

  input.value = '';

  // Add listeners directly
  finish.addEventListener('click', () => {
    eachTodo.classList.toggle('completed');
  })

  remove.addEventListener('click', () => {
    eachTodo.remove();
  })

})
.completed p {
text-decoration: line-through;
opacity: 0.8;
}
<input type="text" id="input">
<button type="button" id="addBtn">Add</button>
<div id="plansDiv"></div>

事件委托

更简洁的解决方案是使用 event delegation and handle all the buttons in a single handler added to the document. Here replacing parentElement with closest('.eachTodo') to avoid the fragility of a specific ancestor depth, and checking which button was clicked using Element.matches().

document.addEventListener('click', (e) => {
  if (e.target.matches('button.finish')){
    e.target.closest('.eachTodo').classList.toggle('completed');
  }
  if (e.target.matches('button.remove')){
    e.target.closest('.eachTodo').remove();
  }
});

const addBtn = document.getElementById('addBtn');
const plansDiv = document.getElementById('plansDiv');
const input = document.getElementById('input');

document.addEventListener('click', (e) => {
  if (e.target.matches('button.finish')){
    e.target.closest('.eachTodo').classList.toggle('completed');
  }
  if (e.target.matches('button.remove')){
    e.target.closest('.eachTodo').remove();
  }
});

addBtn.addEventListener('click', (e) => {
  if (input.value === '') {
    return;
  }

  // adding elements to the body;
  const eachTodo = document.createElement('div');
  eachTodo.classList.add('eachTodo');
  const textName = document.createElement('p');
  textName.textContent = input.value;
  const btns = document.createElement('div');
  btns.classList.add('btns');
  
  const finish = document.createElement('button');
  finish.classList.add('finish');
  finish.textContent = 'Finish';
  finish.type = 'button';
  
  const remove = document.createElement('button');
  remove.classList.add('remove');
  remove.textContent = 'Remove';
  remove.type = 'button';
  
  btns.append(finish, remove);
  eachTodo.append(textName, btns);
  plansDiv.append(eachTodo);

  input.value = '';
});
.completed p {
text-decoration: line-through;
opacity: 0.8;
}
<input type="text" id="input">
<button type="button" id="addBtn">Add</button>
<div id="plansDiv"></div>


请注意,您还可以通过在创建按钮时指定 type="button" 来避免 e.preventDefault() 的必要性。参见:The Button element: type