取消for循环中多个项目的待定setTimeouts

问题描述:

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

I'm trying to create a cool little micro interaction, but I'm running into a minor issue.

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>

如果您连续切换得太快,则最终将显示某些项目,而最终将隐藏其他项目.

If you toggle in too quick of a succession, some items will ultimately be displayed, whereas others will ultimately be hidden.

这是由于以下事实:当发布新的setTimeouts集时,仍有待执行的setTimeouts待执行.

This is due to the fact that there are pending setTimeouts that have yet to be executed when the new set of setTimeouts are issued.

显然,解决此问题的方法很多,例如不反转动画的顺序,等到动画完全完成后再允许反转等,但是我宁愿不做出这样的妥协.

Obviously there are ways around this issue, like not reversing the order of the animation, waiting until the animation is completely finished before allowing the reverse, et cetera, but I would rather not make such compromises.

我尝试在ifelse块中使用和切换全局布尔值,然后在setTimeout块中使用附加的if/else语句,但这没有用.

I've tried using and toggling a global Boolean in the if and else blocks, and then using an additional if/else statement in the setTimeout block, but this didn't work.

我还尝试了在应用新的transform值之前动态设置transition延迟,而不是依赖无效的setTimeout.

I also tried setting transition delays on the fly before applying the new transform values, instead of relying on setTimeout, which didn't work.

是否有简单的方式来取消或忽略旧的周期中的任何待处理的setTimeouts?

Is there a simple way to cancel or ignore any pending setTimeouts from the older cycle?

我将简化您的逻辑,并考虑transition-delay您仅需切换一个类的地方.诀窍是,当我们切换类以获得所需效果时,您的元素会有不同的延迟.

I would simplify your logic and consider transition-delay where you only need to toggle a class. The trick is to have a different delay for your elements when we toggle the class to have the desired effect.

使用此配置,您将不会有任何问题,因为自从将类添加到其父元素以来,所有元素都将具有相同的状态.

With this configuration you won't have any issue because all the element will have the same state since the class is added to their parent element.

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代码:

We can simplify the CSS code by grouping the elements with the same 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),
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>