How to manage the counter with javascript

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

Answers (1)

Pellay
Pellay

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

Related Questions