如何在一个HTML元素上循环使用类别(附代码)

68 阅读5分钟

假设你有三个HTML类,而一个DOM元素一次只能有其中一个。

<div class="state-1"></div>
<div class="state-2"></div>
<div class="state-3"></div>

现在你的工作是让它们轮流使用。也就是说,在一个HTML元素上循环使用类。当某些事件发生时,如果该元素上有state-1 ,则删除state-1 ,并添加state-2 。如果它有state-2 ,则删除它,并添加state-3 。在最后一种状态下,删除它,并循环到state-1

Example of how to Cycle Through Classes on an HTML Element. Here a large <button> with an <svg> inside cycles through state-1, state-2, and state-3 classes, turning from red to yellow to green.

值得注意的是,我们在这里谈论的是3个以上的类。DOM有一个.classList.toggle() 的函数,甚至有一个将条件作为第二个参数的函数,但那主要是在两类的开/关情况下有用,而不是在类之间循环。

为什么呢?有很多原因。改变一个类的名字可以给你很多权力来重新改变DOM中的样式,而像这样的状态管理是现代网络开发的基石。但具体来说,在我的案例中,我想做FLIP动画,我改变一个布局并在不同的状态之间触发一个Tween动画。

小心现有的类!我看到有一些想法覆盖了.className ,这对DOM元素上可能存在的其他类并不友好。所有这些都是以这种方式来循环使用类的 "安全 "选择。

因为这是编程,有很多方法可以做到这一点。让我们来介绍一下其中的一些--*为了好玩。*我在推特上讨论过这个问题,所以这些解决方案中有很多是来自参与讨论的人。

用一个冗长的if/else语句来循环处理类。

这是我一开始做的,以循环通过类。我的大脑就是这样工作的。只要写出非常具体的指令,说明你想发生什么。

if (el.classList.contains("state-1")) {
  el.classList.remove("state-1");
  el.classList.add("state-2");
} else if (el.classList.contains("state-2")) {
  el.classList.remove("state-2");
  el.classList.add("state-3");
} else {
  el.classList.remove("state-3");
  el.classList.add("state-1");
}

我并不介意这里的冗长,因为对我来说,发生了什么是超级清楚的,而且会很容易回到这段代码,并 "推理它",正如他们所说的。你可以认为冗长的代码是一个问题--肯定有一种方法可以用更少的代码在类中循环。但更大的问题是,它的可扩展性不强。没有配置的迹象(例如,很容易改变类的名称),也没有简单的方法来增加类,或删除它们。

我们至少可以使用常量。

const STATE_1 = "state-1";
const STATE_2 = "state-2";
const STATE_3 = "state-3";

if (el.classList.contains(STATE_1)) {
  el.classList.remove(STATE_1);
  el.classList.add(STATE_2);
} else if (el.classList.contains(STATE_2)) {
  el.classList.remove(STATE_2);
  el.classList.add(STATE_3);
} else {
  el.classList.remove(STATE_3);
  el.classList.add(STATE_1);
}

但这并没有什么不同或更好。

RegEx掉旧的类,递增状态,然后重新添加

这一条来自于Tab Atkins。由于我们知道类的格式,state-N ,我们可以寻找,拔掉数字,用一个小三元组来递增(但不能高于最高状态),然后添加/删除类,作为循环使用的方式。

const oldN = +/\bstate-(\d+)\b/.exec(el.getAttribute('class'))[1];
const newN = oldN >= 3 ? 1 : oldN+1;
el.classList.remove(`state-${oldN}`);
el.classList.add(`state-${newN}`);

找到类的索引,然后删除/添加

循环使用类的一系列技术都是围绕着在前面设置一个类的数组展开的。这可以作为循环浏览类的配置,我认为这是一个聪明的方法。一旦你有了这个,你就可以找到相关的类来添加和删除它们。这个是来自Christopher Kirk-Nielsen

const classes = ["state-1", "state-2", "state-3"];
const activeIndex = classes.findIndex((c) => el.classList.contains(c));
const nextIndex = (activeIndex + 1) % classes.length;

el.classList.remove(classes[activeIndex]);
el.classList.add(classes[nextIndex]);

克里斯托弗有一个很好的想法,让添加/删除技术也变得更短。事实证明,它是相同的...

el.classList.remove(classes[activeIndex]);
el.classList.add(classes[nextIndex]);

// Does the same thing.
el.classList.replace(classes[activeIndex], classes[nextIndex]);

Mayank有一个类似的想法,通过在一个数组中找到类来循环使用,只不过不是使用classList.contains() ,而是检查当前DOM元素上的类和数组中的类。

const states = ["state-1", "state-2", "state-3"];
const current = [...el.classList].find(cls => states.includes(cls));
const next = states[(states.indexOf(current) + 1) % states.length];
el.classList.remove(current);
el.classList.add(next);

这个想法的变种是最常见的。这里是Jhey的这里是Mike Wagz的,它设置了向前和向后移动的函数。

级联替换语句

说到那个replace API,Chris Calo有一个聪明的想法,你用or 操作符把它们串联起来,并依靠它在工作或不工作时返回真/假的事实。因此,你把这三个都做了,其中一个就会工作!"。

 el.classList.replace("state-1", "state-2") ||
 el.classList.replace("state-2", "state-3") ||
 el.classList.replace("state-3", "state-1");

Nicolò Ribaudo也得出了同样的结论。

只要在类号中循环即可

如果你预先配置了一个1 ,你可以在1-3班中循环,并在此基础上添加/删除它们。这来自Timothy Leverett,他在同一条推文中列出了另一个类似的选项。

// Assumes a `let s = 1` upfront
el.classList.remove(`state-${s + 1}`);
s = (s + 1) % 3;
el.classList.add(`state-${s + 1}`);

使用data-* 属性来代替

数据属性具有相同的特异性能力,所以我对这一点没有异议。实际上,它们在状态处理方面可能更清晰,但更好的是,它们有一个特殊的API,使它们很好地被操纵。Munawwar Firoz有一个想法,把这个问题归结为一个单行本。

el.dataset.state = (+el.dataset.state % 3) + 1

一个数据属性的状态机

你可以指望David Khourshid会准备好一个状态机。

const simpleMachine = {
  "1": "2",
  "2": "3",
  "3": "1"
};
el.dataset.state = simpleMachine[el.dataset.state];

你几乎肯定会想要一个函数

给自己一点抽象性,对吗?许多想法都是这样写代码的,但到目前为止,我已经把它移出来,以专注于想法本身。在这里,我将把函数留在里面。这个来自Andrea Giammarchi,其中一个用于循环使用类的独特函数是提前设置好的,然后你根据需要调用它。

const rotator = (classes) => ({ classList }) => {
  const current = classes.findIndex((cls) => classList.contains(cls));
  classList.remove(...classes);
  classList.add(classes[(current + 1) % classes.length]);
};

const rotate = rotator(["state-1", "state-2", "state-3"]);
rotate(el);