我怎样才能实现同时在所有柜台停止的计数?

How can I implement a countup that stops at all counters simultaneously?

我已经实现了一个计数,该计数动画达到数据目标。如何调整计数器使所有计数器同时停止?

或者有更好的实现方式吗?

function animationEffect(){

const counters = document.querySelectorAll('.counter');
const speed = 2000;

counters.forEach((counter) => {
      const updateCount = () => {
          const target = + counter.getAttribute('data-target');
          const count = + counter.innerText;
          const inc = target / speed;
          if(count < target) {
              counter.innerText = Math.ceil(count + inc);
              setTimeout(updateCount, 1);
          } else {
              counter.innerText = target;
          }
      }
      updateCount();
  });
}
<div class="counter" data-target="299" onmouseover="animationEffect()">0</div>
<div class="counter" data-target="1299" onmouseover="animationEffect()">0</div>
<div class="counter" data-target="99" onmouseover="animationEffect()">0</div>

我不知道它是否有帮助,但是当我更改此行时它开始工作了: const inc = target / speed;

对此: const inc = 1/(speed / target);

AND 去掉 Math.ceil() 因为这个 const speed = 2000; 实际上是一个步骤创建浮动值。也许尝试更小的 speed

编辑 稍微调整一下,我们得到一个没有浮动值的平滑答案 >:D

function animationEffect(){

if(animationEffect.called){return null}
animationEffect.called=true
//these 2 lines prevent this function from being called multiple times to prevent overlapping
//the code works without these lines though so remove them if u don't want them

const counters = document.querySelectorAll('.counter');
const incrementConstant = 2; //the higher the number the faster the count rate, the lower the number the slower the count rate
const speed = 2000;

counters.forEach((counter) => {
      const updateCount = () => {
          const target = + counter.getAttribute('data-target');
          counter.storedValue=counter.storedValue||counter.innerText-0; //saving a custom value in the element(the floating value)
          const count = + counter.storedValue; //accessing custom value(and rounding it)
          const inc = incrementConstant/(speed / target); //the math thanks to @Tidus
          if(count < target) {
              counter.storedValue=count+inc
              counter.innerText = Math.round(counter.storedValue);
              setTimeout(updateCount, 1);
          } else {
              counter.innerText = target;
          }
      }
      updateCount();
  });
}
<div class="counter" data-target="299" onmouseover="animationEffect()">0</div>
<div class="counter" data-target="1299" onmouseover="animationEffect()">0</div>
<div class="counter" data-target="99" onmouseover="animationEffect()">0</div>

更新答案

看来我误解了 at the same time 的意思,但像这样使用 setTimeout 仍然是一个非常糟糕的做法。这是我的看法:

const counters = Array.from(document.querySelectorAll(".counter"));
const counterValues = [];
const speed = 500;

const updateCount = (counter, target, count, index) => {
  const inc = target / speed;
  counterValues[index] = count + inc;
  if (count < target) {
    counter.innerText = Math.floor(counterValues[index]);
  } else {
    counter.innerText = target;
  }
};

counters.forEach((counter, index) => {
  counterValues.push(0)
  const interval = setInterval(() => {
    const target = +counter.getAttribute("data-target");
    const count = counterValues[index];
    if (target !== count) {
      updateCount(counter, target, count, index)
    } else {
      clearInterval(interval);
    }
  }, 1)
});
<div class="counter" data-target="32">0</div>
<div class="counter" data-target="3000">0</div>
<div class="counter" data-target="10">0</div>

请检查我的旧答案,了解为什么 setInterval 更适合这个问题。 除此之外,这是这段代码中发生的事情: 我定义了一个名为 counterValues 的数组,它将以浮点格式保存计数值。在您的示例中,当您再次存储要在以后的计算中使用的上限数字时,您没有进行正确的计算。

如果我没记错的话,你的一个计数器必须增加 0.145,而你每次都将它增加 1。顺便说一句,地板是正确的方法,因为它在真正到达目标之前不会到达目标。如果目标是 10,但你的计数器是 9.5,它会在你的代码中写成 10,尽管它还没有出现。

updateCount几乎是一样的功能。它现在使用 floor。它通过使用先前的浮动值更新计数器的数量,然后在写入 DOM 时,它使用 floored 值。

对于每个计数器,它添加一个间隔,该间隔将更新计数器并在计数器达到目标值时自行取消。

为了简单起见,我使用了共享状态和索引计算。

旧答案

如果您将 this 代码粘贴到代码顶部并 运行,当您登录 window.activeTimers 时,您会看到定义了数百个计时器。这是因为每次调用 updateCount 时,您都在为 updateCount 设置一个新的计时器。虽然你的 animationEffect 函数就像你程序的主要函数,但如果你每次将鼠标悬停在你的计数器上时调用它,它就会设置新的计时器,这意味着每次都会更快地更新你的计数器。综上所述,您目前完全没有控制权。

对于定期调用,您应该使用 setInterval。它需要一个函数和一个延迟参数(还有可选的参数。您可以查看文档)。它repeatedly calls a function or executes a code snippet, with a fixed time delay between each call (From Mozilla docs)。它还 returns 一个间隔 ID,以便您稍后可以取消它(这意味着我们可以控制它)。

所以在你的情况下,只是为了在所需的时刻停止,你应该做的第一件事就是摆脱对 animationEffect 的 onmouseover 调用,并添加一个按钮来停止执行 updateCounters。

<div class="counter" data-target="299">0</div>
<div class="counter" data-target="1299">0</div>
<div class="counter" data-target="99">0</div>
<button id="stop">Stop</button>

分配变量 countersspeed 后,我们可以定义一个数组来保存间隔的 ID,以便稍后取消它们。

  const counters = document.querySelectorAll(".counter");
  const speed = 2000;
  
  const cancelButton = document.getElementById('stop')
  const countIntervals = [];
  cancelButton.addEventListener('click', () => {
    countIntervals.forEach(interval => clearInterval(interval))
  })

如您所见,我为我们的按钮定义了一个事件侦听器。当您单击它时,它将迭代 countIntervals 中的间隔 ID 并清除这些间隔。为简单起见,我没有实现暂停和重置等功能,并尽量不对您的代码进行太多更改。您可以稍后进行实验。

现在您应该做的第一件事是注释或删除 if 语句中的 setTimeout 行。然后我们将返回的间隔 ID 推送到我们的数组 countIntervals:

  counters.forEach((counter) => {
    const updateCount = () => {
      const target = +counter.getAttribute("data-target");
      const count = +counter.innerText;
      const inc = target / speed;
      if (count < target) {
        counter.innerText = Math.ceil(count + inc);
        // setTimeout(updateCount, 1);
      } else {
        counter.innerText = target;
      }
    };
    countIntervals.push(setInterval(updateCount, 10))
  });

现在,一旦您按下 Stop 按钮,您的计数器就会停止。我忽略了速度功能,但是如果您了解 setInterval 就可以轻松实现它。