使用 'setInterval' 和 'setTimeout' 循环不起作用

Loop with 'setInterval' and 'setTimeout' doesn't work

我正在尝试制作一些卡片的动画,这些卡片应该从右侧进入屏幕,在中间停顿一会儿,然后向左消失,无限循环。 这是我试过的:

function startAnimation(elem) {
  $('#' + elem).fadeIn(150).animate({
    left: '0'
  }, 1500);
}

function endAnimation(elem) {
  $('#' + elem).animate({
    left: '-200%'
  }, 1500);

  $('#' + elem).fadeOut(100).animate({
    left: '200%'
  }, 300);
}

function scrollCards(elem, n) {
  startAnimation(elem);

  setTimeout(function() {
    endAnimation(elem);
  }, 700);

  elem += 1;
  elem = elem == n ? 0 : elem;
  return elem;
}

n = 3;
var card = 0
var firstAnimationDone = false;
$('#0').fadeIn(150);

setInterval(function() {
  if (!firstAnimationDone) {
    endAnimation(card);
    card = 1;
  }
  card = scrollCards(card, n);
  firstAnimationDone = true;
}, 4500);
/* (boxArticle is here just to keep static the part of the page where the animation takes place) */

.boxArticle {
  overflow: hidden;
  height: 100px;
}

.boxAchievements {
  position: relative;
  height: 100px;
  width: 100%;
  left: 200%;
  top: 5px;
  display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="boxArticle">
  <article class="boxAchievements" id="0">
    <h2>My achievements</h2>
    <p>Write 1</p>
  </article>
  <article class="boxAchievements" id="1">
    <h2>My achievements</h2>
    <p>Write 2</p>
  </article>
  <article class="boxAchievements" id="2">
    <h2>My achievements</h2>
    <p>Write 3</p>
  </article>
</div>

当我将 setTimeout 添加到 scrollCards 函数时,它会在中间停止很长时间,无论我在方法中输入的间隔有多长,它都会使循环不同步, 所以我有 2 张卡片同时移动。

您可以使用 CSS animation 规则来用更少的代码实现您想要的。下面的解决方案使用了一个技巧,使无限动画能够 运行 并在迭代之间有延迟(例如,参见 )。

简而言之,动画持续时间的设置考虑了延迟,@keyframes 通过保持相同的动画 属性 值从某个点到 100%(即如果需要 2 秒,并且延迟为8s,然后将持续时间设置为8+2=10s并完成属性变化100*2/10=20%)。

然后您可以随时添加 class 和 animation。要对齐动画,请按顺序添加 classes,步长等于:duration + delay / number of elements.

请注意,由于删除了 fadeIn / fadeOut 方法调用和 display: none; 规则,您的 CSS 已更改为正确对齐 <article> 元素。

(() => {

  $('#0').addClass("middle");
  
  setTimeout(() => $("#1").addClass("middle"), 5e3);
  
  setTimeout(() => $("#2").addClass("middle"), 9e3);
  
})();
body {
  margin: 0;
}

:root {
  --middle : calc(50% - 25vw / 2);
  --left   : calc(0% - 25vw);
  
  --duration : 12s;
}

.boxArticle {
  position: relative;
  overflow: hidden;
  height: 100vh;
  width: 100vw;
}

.boxAchievements {
  position: absolute;
  height: 100px;
  width: 25vw;
  left: 200%;
  top: 5px;
}

.middle {
  animation: 
    middle var(--duration) linear 0s normal infinite forwards running,
    left   var(--duration) linear 0s normal infinite forwards running;
}

@keyframes middle {
  8.3%, 100% { left: var(--middle); }
}

@keyframes left {
  8.3%, 24.9% { left: var(--middle); }
  33.2%, 100%  { left: var(--left); }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="boxArticle">
  <article class="boxAchievements" id="0">
    <h2>My achievements</h2>
    <p>Write 1</p>
  </article>
  <article class="boxAchievements" id="1">
    <h2>My achievements</h2>
    <p>Write 2</p>
  </article>
  <article class="boxAchievements" id="2">
    <h2>My achievements</h2>
    <p>Write 3</p>
  </article>
</div>


还有一些关于您代码段中代码的注释:

  1. 不要混用变量类型。尽管 JavaScript 允许这样做,但这对于任何将阅读您的代码(包括一年后的您)的人来说都是噩梦之源。特别是,scrollCards 有一个参数 elem,它应该是 Element,而不是 number(反之亦然)。

  2. 使用 recursive setTimeout 而不是 setInterval - 后者排队函数调用 不管 是否之前的动画是否完成(还有其他原因使用递归 setTimeout 超出问题范围)。

  3. var 声明 n(更好的是 - 不要声明任何全局变量,但至少避免通过省略声明关键字来创建 implied globals)。

  4. setTimeout 调用不能保证在指定的时间后 运行 因为它们是异步的 - 根据页面加载,完全不同步动画的风险随着时间。

    缓解这种情况的一种方法是使用 promises 等待超时触发,但将项目动画与之对齐可能是一项艰巨的任务。作为示例,以下是如何让 scrollCards 等待 endAnimation 发生:

(() => {
  const now = () => new Date().toISOString();
  const startAnimation = (elem) => console.log(`started animation at ${now()}`);
  const endAnimation = (elem) => console.log(`ended animation at ${now()}`);
  
  async function scrollCards(elem, n) {
    startAnimation(elem);

    //assuming endAnimation is synchronous
    await new Promise((resolve) => setTimeout((elem) => resolve(endAnimation(elem)), 700, elem));

    elem += 1; //see #1 - this is error-prone
    elem = elem == n ? 0 : elem;
    return elem;
  };
    
  scrollCards(0,1);
})();