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

时间:2019-05-14 22:35:25

标签: javascript html css for-loop ecmascript-6

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

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块中使用和切换全局布尔值,然后在if/else块中使用附加的setTimeout语句,但这没用。

我还尝试在应用新的transition值之前动态设置transform延迟,而不是依靠setTimeout来解决这个问题。

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

2 个答案:

答案 0 :(得分:2)

我将简化您的逻辑,并考虑在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>

答案 1 :(得分:0)

This anwer显示了清除所有<CustomTabsNavigator isAdmin={true} /> 的好方法-只需将其添加到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;
}