Reputation: 505
I have a counter in my project. In this type, the counters should not start when the page loads, but the counters should start working when the user scrolls and reaches the desired position on the page. This is done according to the following code. But there is a problem, and that is , when the user scrolls to the desired position of the counters, the desired numbers appear immediately and the numbers do not move forward one by one. For example, a counter with the number 5 scrolls from zero to 5 immediately. (0 => 5)not(0, 1, 2, 3, 4, 5). How can I solve this problem? Thanks in advance for your guidance.
const counter = (EL) => {
const duration = 10000;// Animate all counters equally for a better UX
const start = parseInt(EL.textContent, 10);// Get start and end values
const end = parseInt(EL.dataset.counter, 10); // PS: Use always the radix 10!
if (start === end) return;// If equal values, stop here.
const range = end - start;// Get the range
let curr = start; // Set current at start position
const loop = (raf) => {
if (raf > duration) raf = duration; // Stop the loop
const frac = raf / duration; // Get the time fraction
const step = frac * range; // Calculate the value step
curr = start + step; // Increment or Decrement current value
EL.textContent = parseInt(curr, 10); // Apply to UI as integer
if (raf < duration) requestAnimationFrame(loop); // Loop
};
requestAnimationFrame(loop); // Start the loop!
};
window.addEventListener('scroll', function(){
let heightScroll = document.body.scrollTop || document.documentElement.scrollTop;
let height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
let scroll = Math.round((heightScroll/height) * 100);
if(scroll > 14 && scroll < 17){
document.querySelectorAll("[data-counter]").forEach(counter);
}
})
<div style="height: 400px;border: 2px solid red;"></div>
<section class="business" id="business">
<div class="counter">
<div class="counter--inner">
<span>+</span>
<span data-counter="5" class="count">0</span>
<span>Acceptance Rate</span>
</div>
</div>
<div class="counter">
<div class="counter--inner">
<span>+</span>
<span data-counter="10000" class="count">0</span>
<span>Users</span>
</div>
</div>
<div class="counter">
<div class="counter--inner">
<span>+</span>
<span data-counter="5000" class="count">0</span>
<span>Successful Students</span>
</div>
</div>
<div class="counter">
<div class="counter--inner">
<span>+</span>
<span data-counter="95" class="count">0</span>
<span>Years</span>
</div>
</div>
</section>
when the user scrolls to the desired position of the counters, the desired numbers appear immediately and the numbers do not move forward one by one. For example, a counter with the number 5 scrolls from zero to 5 immediately. (0 => 5) not(0, 1, 2, 3, 4, 5). How can I solve this problem? Thanks in advance for your guidance.
Upvotes: 1
Views: 230
Reputation: 802
The issue appeared to be you are directly comparing the high res timestamp to the duration. The problem with this is this timestamp starts when the DOM is loaded and starts counting, when you request requestAnimationFrame
raf could already be a few seconds or more in, you need to have a variable hold the timestamp for the moment you request the frame and then use the offset to calculate the actual duration passed.
You'll notice in yours, if you wait 3 seconds to start the counter, it's already 3 seconds in.
const counter = (EL) => {
const duration = 10000; // Animate all counters equally for a better UX
const start = parseInt(EL.textContent, 10); // Get start and end values
const end = parseInt(EL.dataset.counter, 10); // PS: Use always the radix 10!
if (start === end) return; // If equal values, stop here.
const range = end - start; // Get the range
let curr = start; // Set current at start position
let curRaf = null; // Variable to hold the inital High Rez Timestamp received from animationFrame
const loop = (raf) => {
if (!curRaf) curRaf = raf; //Set curRaf to current timeStamp to use as an offset if not already set
let offsetRaf = raf - curRaf; //Calculate the actual time passed since our loop started and use this offset
if (offsetRaf > duration) offsetRaf = duration; // Stop the loop
const frac = offsetRaf / duration; // Get the time fraction
const step = frac * range; // Calculate the value step
curr = start + step; // Increment or Decrement current value
EL.textContent = parseInt(curr, 10); // Apply to UI as integer
if (offsetRaf < duration) requestAnimationFrame(loop); // Loop
};
requestAnimationFrame(loop); // Start the loop!
};
var hasTriggers = false;
window.addEventListener('scroll', function() {
let heightScroll = document.body.scrollTop || document.documentElement.scrollTop;
let height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
let scroll = Math.round((heightScroll / height) * 100);
if (scroll > 14 && scroll < 17) {
if (!hasTriggers) document.querySelectorAll("[data-counter]").forEach(counter);
hasTriggers = true;
}
})
<div style="height: 400px;border: 2px solid red;"></div> <section class="business" id="business"> <div class="counter"> <div class="counter--inner"> <span>+</span> <span data-counter="5" class="count">0</span> <span>Acceptance Rate</span> </div> </div> <div class="counter"> <div class="counter--inner"> <span>+</span> <span data-counter="10000" class="count">0</span> <span>Users</span> </div> </div> <div class="counter"> <div class="counter--inner"> <span>+</span> <span data-counter="5000" class="count">0</span> <span>Successful Students</span> </div> </div> <div class="counter"> <div class="counter--inner"> <span>+</span> <span data-counter="95" class="count">0</span> <span>Years</span> </div> </div> </section>
Upvotes: 1