Home > Net >  Change interval of setTimeout such that a countdown from 100 slows down and should take exactly 10 s
Change interval of setTimeout such that a countdown from 100 slows down and should take exactly 10 s

Time:07-28

Based on this SO answer: Changing the interval of SetInterval while it's running

I created this mini Svelte-Repl: https://svelte.dev/repl/ae3987eff26446b49af4a85d029acd80?version=3.49.0

Which looks like this:

<script>
let display = 100
let interval = 1;
let myFunction = function() {
    display--
    interval *= 1.07;
    setTimeout(myFunction, interval);
}
setTimeout(myFunction, interval);
</script>

{#if display > 0}
  {display}
{:else}
  End
{/if}

So the function myFunction calls itself while changing (increasing) the interval so that the count-down slows down. Now I would like to count down from 100 and slowly make the interval longer. I was thinking about the Math on how to achieve that, but couldn't work it out. I'd be grateful for any tips:)

Update:

I guess the question is kind of badly expressed. The idea was to:

  • Have a decreasing counter (display) from 100 to 0
  • The speed in which this counter decreases should become smaller, meaning that changing from e.g. 90 to 89 takes less time than from 10 to 9
  • The function in which it decreases does not matter. It can be exponentially, linearly or any other function. My aim was more about knowing how to solve this "generally"
  • The entire process, decreasing the variable display from 100 to 0, should take (can also be roughly) 10 seconds.
  • How would I do the Math?

CodePudding user response:

Instead of changing the interval times, have a fast rate (frame rate, with requestAnimationRate), and calculate the counter's value based on the timestamp (i.e. the time that has elapsed since the start of the count down).

I would go for a formula that does not lead to exponentially increasing delays, but a less "drastic" one. For instance, you could use this formula to translate elapsed time to a counter value:

initialCount - r * sqrt(elapsed_time)

The coefficient r is then determined by the requirement that after 10 seconds this expression should be zero, so that brings us to:

r = initialCount / sqrt(total_duration)

Instead of a square root (i.e. exponent 0.5), you could use another exponent. The derivation of the value of r remains the same then (using that exponent).

Here is an implementation, where I have also displayed the elapsed time, so to verify it works as intended:

let [span, control] = document.querySelectorAll("span");

function countDown(counterStart, duration) {
    const exp = 0.3; // Between 0 and 1: determines the gravity of the slow-down
    let r = counterStart / duration ** exp;
    let startTime;
    
    function loop(timestamp) {
        if (!startTime) startTime = timestamp;
        const elapsed = timestamp - startTime;
        const counter = Math.ceil(counterStart - r * elapsed ** exp);
        span.textContent = Math.max(0, counter);
        control.textContent = Math.floor(elapsed / 100) / 10;
        if (counter > 0) requestAnimationFrame(loop);
    }
    requestAnimationFrame(loop);
}

countDown(100, 10000);
Count down: <span></span><br>
Elapsed time: <span></span>

If you wish to stick with exponentially increasing delays, then use a logarithm instead of a root:

initialCount - r * log(elapsed_time)

The code is very similar:

let [span, control] = document.querySelectorAll("span");

function countDown(counterStart, duration) {
    let r = counterStart / Math.log(duration);
    let startTime;
    
    function loop(timestamp) {
        if (!startTime) startTime = timestamp;
        const elapsed = timestamp - startTime;
        const counter = Math.ceil(counterStart - r * Math.log(elapsed));
        span.textContent = Math.max(0, counter);
        control.textContent = Math.floor(elapsed / 100) / 10;
        if (counter > 0) requestAnimationFrame(loop);
    }
    requestAnimationFrame(loop);
}

countDown(100, 10000);
Count down: <span></span><br>
Elapsed time: <span></span>

  • Related