我怎样才能实现同时在所有柜台停止的计数?
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>
分配变量 counters
和 speed
后,我们可以定义一个数组来保存间隔的 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
就可以轻松实现它。
我已经实现了一个计数,该计数动画达到数据目标。如何调整计数器使所有计数器同时停止?
或者有更好的实现方式吗?
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>
分配变量 counters
和 speed
后,我们可以定义一个数组来保存间隔的 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
就可以轻松实现它。