在HTML列表中点击后,将一个类添加到唯一的元素中。

8

我有一个如下的列表:

 <ul>
   <li class="item">Item 1</li>
   <li class="item">Item 2</li>
   <li class="item">Item 3</li>
 </ul>

我需要在单击时将“active”类分别添加到每个项,并在上一个项上删除它。

类似于这个问题:Vanilla JS remove class from previous selection,但由于要兼容旧浏览器,我需要使用for循环而不是forEach。

我一直在尝试将那个答案适应我的示例:

const items = document.querySelectorAll(".item");

for (let i = 0; i < items.length; i++) {
  const item = items[i];
  item.addEventListener("click", addActiveClass);

  for (let i = 0; i < items.length; i++) {
    const item = items[i];
    item.addEventListener("click", removeClass);
  }
}

function removeClass(e) {
  e.target.classList.remove("active");
}

function addActiveClass(e) {
  e.target.classList.add("active");
}

但是它仍然没有按预期工作 :(

最初所有项目都是活动的还是非活动的? - Shivanshu Gupta
1
你正在为相同的元素添加单击监听器,以删除类并添加类。当点击发生时,它将有效地触发这两个监听器,先添加类,然后立即删除类。不仅如此,你在for循环内部又有一个for循环,这会一遍又一遍地向相同的元素添加多个事件监听器。你链接中提到的内部forEach只是用于删除类,而不是添加一个点击监听器,然后再删除类。 - Patrick Evans
@ShivanshuGupta 不活跃 - Nesta
7个回答

7
我假设您需要从其他项目中删除活动类,然后更新正确的项目。但是,我不确定在您希望支持的旧浏览器中是否可用 classList。您应该查看旧浏览器的polyfill
const items = document.querySelectorAll(".item");

for (let i = 0; i < items.length; i++) {
  const item = items[i];
  item.addEventListener("click", changeActiveClass);
}

function changeActiveClass(e)
{
  for (let i = 0; i < items.length; i++) {
    const item = items[i];
    item.classList.remove('active');
  }
  e.target.classList.add('active');
}

7
你当前的方法会给元素添加多个 click 处理程序,这些程序按顺序依次触发,由于嵌套循环的存在,多次删除和添加类别时结果不可预测。
现有答案使用 O(n) 的解决方案,涉及循环遍历列表中的所有元素,但这并非必要。只需保留对最后一个选定元素的引用,并从该元素中删除.active 类(如果定义了),即可解决问题。

const list = [...document.querySelectorAll("ul li")];
let selectedEl;

for (const el of list) {
  el.addEventListener("click", e => {
    selectedEl && selectedEl.classList.remove("active");
    selectedEl = e.target;
    e.target.classList.add("active");
  });
}
.active {
  background: yellow;
}
<ul>
  <li class="item">Item 1</li>
  <li class="item">Item 2</li>
  <li class="item">Item 3</li>
</ul>

另一种方法是为每个元素设置 tabindex 属性,然后添加 focusblur 事件的监听器(如果需要冒泡,可以使用 focusin/focusout 事件),这样可以更准确地捕获您尝试实现的选择/取消选择语义。将焦点移动到任何位置都将取消选定列表项,不像上面显示的 click 事件方法。

for (const el of [...document.querySelectorAll("ul li")]) {
  el.addEventListener("focus", e => e.target.classList.add("active"));
  el.addEventListener("blur", e => e.target.classList.remove("active"));
}
.active {
  background: yellow;
  outline: none;
}
<ul>
  <li class="item" tabindex="-1">Item 1</li>
  <li class="item" tabindex="-1">Item 2</li>
  <li class="item" tabindex="-1">Item 3</li>
</ul>

如果您希望支持老式浏览器,可以尝试以下方法(未经测试):

var list = document.getElementsByTagName("li");
var selectedEl;

for (var i = 0; i < list.length; i++) {
  list[i].addEventListener("click", function (e) {
    if (selectedEl) {
      selectedEl.className = selectedEl.className
        .split(/\s+/)
        .filter(function (e) { e !== "active"; })
        .join(" ");
    }

    selectedEl = e.target;
    e.target.className += " active";
  });
}
.active {
  background: yellow;
}
<ul>
  <li class="item">Item 1</li>
  <li class="item">Item 2</li>
  <li class="item">Item 3</li>
</ul>

或者

var list = document.getElementsByTagName("li");

for (var i = 0; i < list.length; i++) {
  list[i].addEventListener("focusin", function (e) {
    e.target.className += " active";
  });
  list[i].addEventListener("focusout", function (e) {
    e.target.className = e.target.className
      .split(/\s+/)
      .filter(function (e) { e !== "active"; })
      .join(" ");
  });
}
.active {
  background: yellow;
  outline: none;
}
<ul>
  <li class="item" tabindex="-1">Item 1</li>
  <li class="item" tabindex="-1">Item 2</li>
  <li class="item" tabindex="-1">Item 3</li>
</ul>


4

您需要稍微修改for循环:

for (let i = 0; i < items.length; i++) {
  const clickedItem = items[i];
 
  // Add an event listener to each item
  clickedItem.addEventListener("click", function() {

    // Remove class from all items
    for (let j = 0; j < items.length; j++) {
      const item = items[j];
      item.classList.remove("active");
    }

    // Add class to the clicked item
    clickedItem.classList.add("active");

  });
}

请注意,您也可以使用箭头函数,但是如果您考虑向后兼容性,则最好使用“function”关键字。

4
这似乎是事件委托的一个很好的候选方案。代码可以重写为类似于以下内容:

document.addEventListener("click", addActive);

function addActive(evt) {
  if (evt.target.classList.contains("item")) {
    // remove .active from all li.active
    const allItems = document.querySelectorAll(".active");
    for (let i=0; i<allItems.length; i += 1) {
      allItems[i].classList.toggle("active");
    }
    // add .active to the current (clicked) item
    evt.target.classList.toggle("active");
  }
}
li.item {
  cursor: pointer;
}
.active {
  color: red;
}
<ul>
   <li class="item">Item 1</li>
   <li class="item">Item 2</li>
   <li class="item">Item 3</li>
 </ul>


3

您可以尝试像这样:

const items = document.querySelectorAll(".item");

for (let i = 0; i < items.length; i++) {
    const item = items[i];
    item.addEventListener("click", toggleActive);
}

function cleanActiveClasses() {
    for (let i = 0; i < items.length; i++) {
        const item = items[i];
        item.className = item.className.replace(/(?:^|\s)active(?!\S)/g , '')
    }
}

function toggleActive(e) {
    cleanActiveClasses();
    e.target.className += " active";
}
.active {
  color: red;
}
<ul>
   <li class="item">Item 1</li>
   <li class="item">Item 2</li>
   <li class="item">Item 3</li>
 </ul>

添加了一些CSS,只是为了让您获得视觉反馈。

简而言之:在DOM元素上只有一个侦听器,它会在分配新类之前清除每个元素。

希望它有所帮助。

编辑:由于您要求此项向后兼容,旧的IE版本不支持classList。编辑了一个使用className和正则表达式删除活动类的新版本。


3

这将根据需要添加/删除类。此外,检查并确保您要查找的元素.item实际上位于页面上始终是很好的。

const items = document.querySelectorAll(".item");

for (let i = 0; i < items.length; i++) {
  const item = items[i];

  item.addEventListener("click", () => {
    for (let i = 0; i < items.length; i++) {
      const item = items[i];
      item.classList.remove("active");
    }

    item.classList.add("active");
  });
}
.item:hover {
  cursor: pointer;
}
.active {
  color: blue;
}
<div>
  <ul>
     <li class="item">Item 1</li>
     <li class="item">Item 2</li>
     <li class="item">Item 3</li>
   </ul>
 </div>


1
@ggorlen,我不知道qSA会返回0,好知道!我会更新代码以删除其他元素中的active。 - nihiser

2

您只需在click事件的一个处理程序中执行此操作。

const items = document.querySelectorAll(".item");

for (let i = 0; i < items.length; i++) {
  const item = items[i];
  item.addEventListener("click", (e) => toggleActiveClass(e, items));
}

function toggleActiveClass(e) {
  for (let i = 0; i < items.length; i++) {
    const item = items[i];
    item.classList.remove("active");
  }
  e.target.classList.add("active");
}
.item.active {
  color: red;
}
<ul>
   <li class="item">Item 1</li>
   <li class="item">Item 2</li>
   <li class="item">Item 3</li>
 </ul>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接