取消 for 循环中多个项目的挂起设置超时



我正在尝试创建一个很酷的小微交互,但我遇到了一个小问题。

document.querySelector('button').onclick = function(){
const
items = document.querySelector('nav').children
if (items[0].getBoundingClientRect().top >= document.querySelector('nav').getBoundingClientRect().bottom){
// start showing elements, starting from the left side
for (let i = 0; i < items.length; i++){
setTimeout(function(){
items[i].style.transform = 'translateY(0)'
}, i * 200)
}
} else {
// start hiding elements, starting from the right side
for (let i = 0; i < items.length; i++){
setTimeout(function(){
items[i].style.transform = 'translateY(100%)'
}, (items.length-1 - i) * 200)
}
}
}
button {
width: 100px;
height: 50px;
}
nav {
width: 50vw;
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-gap: 10px;
background: red;
}
nav > a {
width: 100%;
height: 50px;
transition: .5s transform;
transform: translateY(100%);
opacity: 0.5;
background: lime;
}
<button>Toggle</button>
<nav>
<a href=''></a>
<a href=''></a>
<a href=''></a>
<a href=''></a>
</nav>

如果切换得太快,某些项目最终将显示,而其他项目最终将被隐藏。

这是因为在发布新的setTimeouts集时,仍有待处理setTimeouts尚未执行。

显然有办法解决这个问题,比如不颠倒动画的顺序,等到动画完全完成才允许反转,等等,但我宁愿不做这样的妥协。

我尝试在ifelse块中使用和切换全局布尔值,然后在setTimeout块中使用额外的if/else语句,但这不起作用。

我还尝试在应用新的transform值之前即时设置transition延迟,而不是依赖setTimeout,这不起作用。

有没有一种简单的方法可以取消或忽略旧周期中的任何待处理setTimeouts

我会简化你的逻辑,并考虑transition-delay你只需要切换一个类的地方。诀窍是,当我们切换类以获得所需的效果时,为您的元素设置不同的延迟。

使用此配置,您将不会有任何问题,因为所有元素都将具有相同的状态,因为该类已添加到其父元素中。

var nav = document.querySelector('nav');
document.querySelector('button').onclick = function(){
nav.classList.toggle('top');
}
button {
width: 100px;
height: 50px;
}
nav {
width: 50vw;
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-gap: 10px;
background: red;
--d:0.2s;
}
nav > a {
width: 100%;
height: 50px;
transition: .5s transform;
transform: translateY(100%);
opacity: 0.5;
background: lime;
}
nav.top > a {
transform: translateY(0);
}
nav > a:nth-last-child(1) { transition-delay:calc(0 * var(--d));}
nav > a:nth-last-child(2) { transition-delay:calc(1 * var(--d));}
nav > a:nth-last-child(3) { transition-delay:calc(2 * var(--d));}  
nav > a:nth-last-child(4) { transition-delay:calc(3 * var(--d));}
nav.top > a:nth-child(1) { transition-delay:calc(0 * var(--d));}
nav.top > a:nth-child(2) { transition-delay:calc(1 * var(--d));}
nav.top > a:nth-child(3) { transition-delay:calc(2 * var(--d));}  
nav.top > a:nth-child(4) { transition-delay:calc(3 * var(--d));}
<button>Toggle</button>
<nav>
<a href=''></a>
<a href=''></a>
<a href=''></a>
<a href=''></a>
</nav>

我们可以通过对具有相同延迟的元素进行分组来简化 CSS 代码:

var nav = document.querySelector('nav');
document.querySelector('button').onclick = function(){
nav.classList.toggle('top');
}
button {
width: 100px;
height: 50px;
}
nav {
width: 50vw;
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-gap: 10px;
background: red;
--d:0.2s;
}
nav > a {
width: 100%;
height: 50px;
transition: .5s transform;
transform: translateY(100%);
opacity: 0.5;
background: lime;
}
nav.top > a {
transform: translateY(0);
}
nav > a:nth-last-child(1),
nav.top > a:nth-child(1) { transition-delay:calc(0 * var(--d));}
nav > a:nth-last-child(2),
nav.top > a:nth-child(2) { transition-delay:calc(1 * var(--d));}
nav > a:nth-last-child(3),
nav.top > a:nth-child(3){ transition-delay:calc(2 * var(--d));}  
nav > a:nth-last-child(4),
nav.top > a:nth-child(4){ transition-delay:calc(3 * var(--d));}
<button>Toggle</button>
<nav>
<a href=''></a>
<a href=''></a>
<a href=''></a>
<a href=''></a>
</nav>

这个 anwer 展示了清除所有setTimeout的好方法 - 只需将其添加到if/else语句的每个部分中:

document.querySelector('button').onclick = function() {
const
items = document.querySelector('nav').children
if (items[0].getBoundingClientRect().top >= document.querySelector('nav').getBoundingClientRect().bottom) {
var id = window.setTimeout(() => {}, 0);
while (id--) {
window.clearTimeout(id);
}
// start showing elements, starting from the beginning
for (let i = 0; i < items.length; i++) {
setTimeout(function() {
items[i].style.transform = 'translateY(0)'
}, i * 200)
}
} else {
var id = window.setTimeout(() => {}, 0);
while (id--) {
window.clearTimeout(id);
}
// start hiding elements, starting from the back
for (let i = 0; i < items.length; i++) {
setTimeout(function() {
items[i].style.transform = 'translateY(100%)'
}, (items.length - 1 - i) * 200)
}
}
}
button {
width: 100px;
height: 50px;
}
nav {
width: 50vw;
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-gap: 10px;
background: red;
}
nav>a {
width: 100%;
height: 50px;
transition: .5s transform;
transform: translateY(100%);
opacity: 0.5;
background: lime;
}
<button>Toggle</button>
<nav>
<a href=''></a>
<a href=''></a>
<a href=''></a>
<a href=''></a>
</nav>

最新更新