假设您有三个 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
。

值得注意的是,我们在这里讨论的是 3 个或更多个类。 DOM 具有 .classList.toggle()
函数,甚至一个函数可以将条件作为第二个参数,但它主要用于两个类的开/关情况,而不是在类之间循环。
原因? 有很多原因。 更改类名为您提供了重新设置 DOM 中样式的强大功能,并且像这样的状态管理是现代 Web 开发的基石。 但具体地说,在我的情况下,我想做 FLIP 动画,在那里我会更改布局并触发不同状态之间的补间动画。
小心 现有类! 我看到了一些覆盖 .className
的想法,这对 DOM 元素上可能存在的其他类不友好。 所有这些都是以这种方式在类之间循环的“安全”选择。
因为这是编程,所以有很多方法可以完成此操作。 让我们覆盖其中很多方法——为了好玩。 我 在 Twitter 上发布了这个问题,因此这些解决方案中的许多来自参与该讨论的人们。
一个冗长的 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);
}
但这并没有太大区别或更好。
从旧类中删除正则表达式,增加状态,然后重新添加
这一个 来自 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]);
Christopher 有一个让添加/删除技巧更简短的好主意。 事实证明它是一样的……
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
运算符将它们链接起来,并依靠它返回 true/false(如果它有效或无效)。 因此,您执行所有三个操作,其中一个操作将起作用!
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);
我从 Kyle Simpson 那里听到了同样的想法,几乎是一字不差。
其他?
在我 原始推文 的回复中还有更多想法,但据我所知,它们是我上面已经分享过的内容的变体。 如果我错过了你的想法,我深感歉意! 请随时在下面的评论中再次分享您的想法。 我看到没有人使用 switch
语句——这可能是一个可能性!
David Desandro 甚至录制了视频,这很棒,因为它逐渐将概念抽象化,直到它简洁但仍然可读且更灵活。
这里有一个演示 Pen,其中包含每个示例的所有代码。 它们是编号的,因此要测试另一个示例,请注释掉未注释的那个,并取消注释另一个示例。
使用 `data-` 属性方法时,无需操作类;只需像这样定义样式
是的,绝对的:将样式与逻辑分离。我知道如果你习惯于使用 bootstrap 和 jquery,它们会进行很多难以调试的 css 切换,那么学习曲线会很高。
Arr.push(arr.unshift())[0]
这是 Andreas 函数的一个变体,它使用 classList.toggle 的可选第二个参数来移除或添加类
这是代码笔:https://codepen.io/Kilian/pen/BamyXaj
另外值得注意的是:
HTMLElement.prototype.matches
比DOMTokenList.prototype.contains
更有用。更棒
如果你的需求表明“循环”可以在元素本身发生,而不是在类上发生——那么你可以在纯 CSS 中做到这一点。
也就是说,如果你正在构建一个“三态”按钮,它会因用户交互(聚焦/模糊)而循环,那么只使用 CSS 会有更简洁的方法。
我不会写出解决方案,因为我在手机上,但它涉及使用 `input[type="radio"]:checked` 对 `z-index` 进行样式化。
我很有兴趣看到完整的纯 css 解决方案;)