skip to Main Content

I want to display the progress of a loop function like this ‘1 of total 1000’. I use this code, but in #mycontent article displayed only the last value of i.

for (i = 1; i <= 1000; i += 1) {
  $('#mycontent article').html(i + ' of total 1000');
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>

<div id="mycontent">
  <article></article>
</div>

2

Answers


  1. JavaScript is single-threaded. In this case that means this loop will run to completion before the UI updates, so the UI will only ever see the last change.

    Taking a step back… How visually fast do you want the UI to update? Even if the UI were updating, this would be so fast that it wouldn’t really matter. How about an interval of, say, 10ms? The keyword there is "interval", as you can use setInterval to achieve this. For example:

    let i = 1;
    let interval = setInterval(function () {
      $('#mycontent article').html(i + ' of total 1000');
      i++;
      if (i > 1000) {
        clearInterval(interval);
      }
    }, 10);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
    <div id="mycontent"><article></article></div>
    Login or Signup to reply.
  2. The execution of your code is blocked until the loop finishes – therefore you see only the last value.
    A JavaScript interval might be used, but instead of setInterval or setTimeout you might use the preferred requestAnimationFrame (for smoother animation and performance):

    const ease = {
      linear: t => t,
      inOutQuad: t => t<.5 ? 2*t*t : -1+(4-2*t)*t,
      // Find out more at: https://gist.github.com/gre/1650294
    };
    
    const counter = (EL) => {
    
      const duration = 4000;
    
      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 timeStart = Date.now();
    
      const loop = () => {
        let elaps = Date.now() - timeStart;
        if (elaps > duration) elaps = duration; // Stop the loop
        const norm = ease.inOutQuad(elaps / duration); // normalised value + easing
        const step = norm * range; // Calculate the value step
        curr = start + step; // Increment or Decrement current value
        EL.textContent = Math.trunc(curr); // Apply to UI as integer
        if (elaps < duration) requestAnimationFrame(loop); // Loop
      };
    
      requestAnimationFrame(loop); // Start the loop!
    };
    
    document.querySelectorAll("[data-counter]").forEach(counter);
    <span data-counter="1000">0</span> of total 1000

    Also, you don’t need JavaScript, (well as soon as Firefox implements this) – it can be done in pure CSS with @property and counter:

    @property --from {
      syntax: '<integer>';
      initial-value: 0;
      inherits: false;
    }
    
    .counter {
      transition: --from 1s;
      counter-reset: int var(--from);
      animation: counter var(--time, 1000) forwards ease-in-out;
    }
    
    .counter::after {
      content: counter(int) " of total 1000";
    }
    
    @keyframes counter {
      to {
        --from: var(--to, 100);
      }
    }
    <span class="counter" style="--from:0; --to:1000; --time:4s;"></span>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search