54

I am updating a numeric value inside an element by doing intervalled ajax requests.

To make the whole thing a bit more alive, I want to count from the current value to the new one, by partially in- or decreasing the value over a time of n sec.

So basically something like this:

<div id="value">100</div>
<script type="text/javascript">
    /** Decrease $value (over a time of 2 seconds) till it reaches 25 */
    $value.increaseAnimation(-75, {duration:2});
</script>

Is there a javascript library for doing so?

doğukan
  • 23,073
  • 13
  • 57
  • 69
RienNeVaPlu͢s
  • 7,442
  • 6
  • 43
  • 77

11 Answers11

122

You can just code it yourself pretty simply:

function animateValue(id, start, end, duration) {
    if (start === end) return;
    var range = end - start;
    var current = start;
    var increment = end > start? 1 : -1;
    var stepTime = Math.abs(Math.floor(duration / range));
    var obj = document.getElementById(id);
    var timer = setInterval(function() {
        current += increment;
        obj.innerHTML = current;
        if (current == end) {
            clearInterval(timer);
        }
    }, stepTime);
}

animateValue("value", 100, 25, 5000);
#value {
    font-size: 50px;
}
<div id="value">100</div>

Here's is a more accurate version that self adjusts in case the timer intervals aren't perfectly accurate (which they sometimes aren't):

function animateValue(id, start, end, duration) {
    // assumes integer values for start and end
    
    var obj = document.getElementById(id);
    var range = end - start;
    // no timer shorter than 50ms (not really visible any way)
    var minTimer = 50;
    // calc step time to show all interediate values
    var stepTime = Math.abs(Math.floor(duration / range));
    
    // never go below minTimer
    stepTime = Math.max(stepTime, minTimer);
    
    // get current time and calculate desired end time
    var startTime = new Date().getTime();
    var endTime = startTime + duration;
    var timer;
  
    function run() {
        var now = new Date().getTime();
        var remaining = Math.max((endTime - now) / duration, 0);
        var value = Math.round(end - (remaining * range));
        obj.innerHTML = value;
        if (value == end) {
            clearInterval(timer);
        }
    }
    
    timer = setInterval(run, stepTime);
    run();
}

animateValue("value", 100, 25, 5000);
#value {
    font-size: 50px;
}
<div id="value">100</div>
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • 1
    @RienNeVaPlus - I added a more accurate version. – jfriend00 Jun 08 '13 at 00:52
  • Thank you jfriend, this works great -- and no need for a huge plugin system :) – NiCk Newman Oct 16 '15 at 14:09
  • +1 This helped me today, thanks. I *(extremely)* crudely amended it to allow for floats as well as integers, otherwise it just keeps on counting...! `current += (end - current < increment? end - current : increment);` – EvilDr Dec 05 '18 at 16:19
  • This is great! Although I do have a question.. @jfriend00.. How would I alter this so the final 20% of the duration the count slows down, creating a dramatic effect? – Robolisk Jul 15 '19 at 20:14
  • @Robolisk - I'd suggest you describe your requirements in a new question. You can refer to this answer in your new question, but describe what you want to do differently. – jfriend00 Jul 15 '19 at 20:54
  • If you have big numbers, the second solution is the way to go. Many thanks! – Chris Rahmé Aug 12 '20 at 17:17
  • The second option is the best! – VanilJS Sep 21 '20 at 16:29
  • Also, if start is equal to end, then the code above has an error. You would need to check if start is equal to end and return in that case as there's nothing to be animated – roshkattu Oct 09 '20 at 14:51
  • @roshkattu - I added that check to the first option above. The second one does not appear to need it as it detects that case just fine. – jfriend00 Oct 09 '20 at 15:08
22

Current solutions do update more often than needed. Here a frame based approach, which is accurate:

function animateValue(obj, start, end, duration) {
  let startTimestamp = null;
  const step = (timestamp) => {
    if (!startTimestamp) startTimestamp = timestamp;
    const progress = Math.min((timestamp - startTimestamp) / duration, 1);
    obj.innerHTML = Math.floor(progress * (end - start) + start);
    if (progress < 1) {
      window.requestAnimationFrame(step);
    }
  };
  window.requestAnimationFrame(step);
}

const obj = document.getElementById('value');
animateValue(obj, 100, -25, 2000);
div {font-size: 50px;}
<div id="value">100</div>
User Rebo
  • 3,056
  • 25
  • 30
7

Now we can animate counters (and a lot of things that cannot be animated before) with CSS variables and new @property. No javascript needed. Currently supports only Chrome and Edge.

@property --n {
  syntax: "<integer>";
  initial-value: 0;
  inherits: false;
}

body {
  display: flex;
}

.number {
  animation: animate var(--duration) forwards var(--timing, linear);
  counter-reset: num var(--n);
  font-weight: bold;
  font-size: 3rem;
  font-family: sans-serif;
  padding: 2rem;
}
.number::before {
  content: counter(num);
}

@keyframes animate {
  from {
    --n: var(--from);
  }
  to {
    --n: var(--to);
  }
}
<div class="number" style="--from: 0; --to: 100; --duration: 2s;"></div>
<div class="number" style="--from: 10; --to: 75; --duration: 5s; --timing: ease-in-out"></div>
<div class="number" style="--from: 100; --to: 0; --duration: 5s; --timing: ease"></div>
doğukan
  • 23,073
  • 13
  • 57
  • 69
6

I had a slightly different approach to this kind of animation. Based on these assumptions:

  1. there is a starting text (as fallback for non-JS browser or google indexing)
  2. this text can contains non-numeric chars
  3. the function take an element directly as first param (as opposed to an element Id)

So, if you want to animate a simple text like "+300% gross margin", only the numeric part will be animated.

Moreover, all params have now a default value for start, end and duration.

https://codepen.io/lucamurante/pen/gZVymW

function animateValue(obj, start = 0, end = null, duration = 3000) {
    if (obj) {

        // save starting text for later (and as a fallback text if JS not running and/or google)
        var textStarting = obj.innerHTML;

        // remove non-numeric from starting text if not specified
        end = end || parseInt(textStarting.replace(/\D/g, ""));

        var range = end - start;

        // no timer shorter than 50ms (not really visible any way)
        var minTimer = 50;

        // calc step time to show all interediate values
        var stepTime = Math.abs(Math.floor(duration / range));

        // never go below minTimer
        stepTime = Math.max(stepTime, minTimer);

        // get current time and calculate desired end time
        var startTime = new Date().getTime();
        var endTime = startTime + duration;
        var timer;

        function run() {
            var now = new Date().getTime();
            var remaining = Math.max((endTime - now) / duration, 0);
            var value = Math.round(end - (remaining * range));
            // replace numeric digits only in the original string
            obj.innerHTML = textStarting.replace(/([0-9]+)/g, value);
            if (value == end) {
                clearInterval(timer);
            }
        }

        timer = setInterval(run, stepTime);
        run();
    }
}

animateValue(document.getElementById('value'));
#value {
    font-size: 50px;
}
<div id="value">+300% gross margin</div>
Luca Murante
  • 317
  • 3
  • 8
5

const counters = document.querySelectorAll('.counters');

counters.forEach(counter => {
  let count = 0;
  const updateCounter = () => {
    const countTarget = parseInt(counter.getAttribute('data-counttarget'));
    count++;
    if (count < countTarget) {
      counter.innerHTML = count;
      setTimeout(updateCounter, 1);
    } else {
      counter.innerHTML = countTarget;
    }
  };
  updateCounter();
});
<p class="counters" data-counttarget="50"></p>
<p class="counters" data-counttarget="100"></p>
<p class="counters" data-counttarget="500"></p>
<p class="counters" data-counttarget="1000"></p>
  • Please don't post only code as answer, but also provide an explanation what your code does and how it solves the problem of the question. Answers with an explanation are usually more helpful and of better quality, and are more likely to attract upvotes. – Tyler2P May 20 '21 at 20:20
4

This works well. However, I needed to use a comma within the number. Below is the updated code which checks for commas. Hope someone finds this useful if they stumble across this post.

function animateValue(id, start, end, duration) {

    // check for commas
    var isComma = /[0-9]+,[0-9]+/.test(end); 
    end = end.replace(/,/g, '');

    // assumes integer values for start and end

    var obj = document.getElementById(id);
    var range = end - start;
    // no timer shorter than 50ms (not really visible any way)
    var minTimer = 50;
    // calc step time to show all interediate values
    var stepTime = Math.abs(Math.floor(duration / range));

    // never go below minTimer
    stepTime = Math.max(stepTime, minTimer);

    // get current time and calculate desired end time
    var startTime = new Date().getTime();
    var endTime = startTime + duration;
    var timer;

    function run() {
        var now = new Date().getTime();
        var remaining = Math.max((endTime - now) / duration, 0);
        var value = Math.round(end - (remaining * range));
        obj.innerHTML = value;
        if (value == end) {
            clearInterval(timer);
        }
        // Preserve commas if input had commas
        if (isComma) {
            while (/(\d+)(\d{3})/.test(value.toString())) {
                value = value.toString().replace(/(\d+)(\d{3})/, '$1'+','+'$2');
            }
        }
    }

    var timer = setInterval(run, stepTime);
    run();
}

animateValue("value", 100, 25, 2000); 
londonfed
  • 1,170
  • 2
  • 12
  • 27
1

HTML

<!DOCTYPE html>
<html>
<head>
    <title>Count</title>
</head>
<body>
    <div id="value">1000</div>
</body>
</html>

JAVASCRIPT snippet

Here is a simple js function decrementing values from a given start number to an end number (object prototype)..

function getCounter(startCount,endcount,time,html){
objects = {
    //you can alternateif you want yo add till you reach the endcount
    startCount:startCount,
    endCount:endcount,
    timer:time
}
this.function = function(){
    let startTm  = objects.startCount,
        timer = objects.timer,
        endCount = objects.endCount;
        //if you want it to add a number just replace the -1 with +1
        /*and dont forget to change the values in the object prototype given a variable of counter*/
    let increment = startTm  < endCount ? 1:-1;
        timmer  = setInterval(function(){
                startTm  += increment;
                html.innerHTML = startTm ;
                if(startTm  == endCount){
                    clearInterval(timmer);
                }
            },timer);
   }
}
// input your startCount,endCount  the timer.....
let doc = document.getElementById('value');

let counter = new getCounter(1000,1,10,doc);
//calling the function in the object
counter.function();

Check out this demo https://jsfiddle.net/NevilPaul2/LLk0bzvm/

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
0

This is what i came up with:

function animateVal(obj, start=0, end=100, steps=100, duration=500) {   
    start = parseFloat(start)
    end = parseFloat(end)

    let stepsize = (end - start) / steps
    let current = start
    var stepTime = Math.abs(Math.floor(duration / (end - start)));
    let stepspassed = 0
    let stepsneeded = (end - start) / stepsize

    let x = setInterval( () => {
            current += stepsize
            stepspassed++
            obj.innerHTML = Math.round(current * 1000) / 1000 
        if (stepspassed >= stepsneeded) {
            clearInterval(x)
        }
    }, stepTime)
}   

animateVal(document.getElementById("counter"), 0, 200, 300, 200)
0

Here is one version where increments grow by some defined multiplier (mul). frameDelay is the time delay for each increment. This looks a bit better if you have values that camn

function cAnimate(id, start, end, frameDelay = 100, mul = 1.2) {
    var obj = document.getElementById(id);
    var increment = 2;
    var current = start;
    var timer = setInterval(function() {
        current += increment;
        increment *= mul;
        if (current >= end) {
            current = end;
            clearInterval(timer);
        }

        obj.innerHTML = Math.floor(current).toLocaleString();

    }, frameDelay);
}

cAnimate("counter", 1, 260000, 50);
Tim Hysniu
  • 1,446
  • 13
  • 24
0

I used a mixture of all of these to create a function that updates a BehaviorSubject.

function animateValue(subject, timerRef, startValue, endValue, duration){
  if (timerRef) {  clearInterval(timerRef); }
  const minInterval = 100;
  const valueRange = endValue - startValue;
  const startTime = new Date().getTime();
  const endTime = startTime + (duration * 1000);
  const interval = Math.max((endTime-startTime)/valueRange, minInterval);

  function run() {
    const now = new Date().getTime();
    const rangePercent = Math.min(1-((endTime-now)/(endTime-startTime)),1);
    const value = Math.round(rangePercent * valueRange+startValue);

    subject.next(value);
    if (rangePercent >= 1) {
      clearInterval(timerRef);
    }
  }

  timerRef = setInterval(run, interval);
  run();
}
Chris
  • 2,727
  • 2
  • 27
  • 28
0
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        *{
            margin: 0;
            padding: 0;
        }
    </style>
</head>
<body style="width: 100vw; height: 100vh; display: flex; align-items: center; justify-content: center;">
    <h1 style="font-size: 50px;" data-value="3212">0</h1>
    <script>
        let e = document.querySelector('h1');
        let v = Number(e.dataset.value);
        let i = Math.floor(v/10);
        let r = v%10;
        function increment() {
            let c = Number(e.innerText);
            if (c<v) {
                if (v-c===i+r) {
                    e.innerText = c+i+r;
                }
                else{
                    e.innerText = c+i;
                };
                setTimeout(increment,200);
            };
        };
        setTimeout(increment, 200);
    </script>
</body>
</html>