Dusttt
Dusttt

Reputation: 29

Display only part of decimal number but not change their real values

I am currently working on a web project that has some incrementation animation and I've created a counter function like this :

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

    counters.forEach(counter => {
        const updateCount = () => {
            const target = +counter.getAttribute('data-target');
            const count = +counter.innerText;

            const speed = target / 1000; //this will not work accurate if using Math.ceil or floor to round number up
           
            if (count < target) {
                counter.innerText = count + speed;
                setTimeout(updateCount,1);
            } else {
                counter.innerText = target;
            }
        }
        updateCount();
    });
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}
html , body {
  width: 100%;height: 100%;
  display: flex;
  justify-content: center;
  flex-direction: column;
  align-items: center;
}
/* HEADER */

.counter {
  font-size: 20px;
  font-weight: bold;
  padding: 5px;
}
<span data-target="20" class="counter">0</span>
    <span data-target="100" class="counter">0</span>
    <span data-target="50" class="counter">0</span>
    <span data-target="250000" class="counter">0</span>

The problem is the number display in the web is decimal. I've tried math.ceil,math.floor, ... But they seem to mess up the speed of the counter . Is there a way of display only the integer part WITHOUT messing with the speed ?

Upvotes: 1

Views: 120

Answers (3)

Fabrizio Calderan
Fabrizio Calderan

Reputation: 123397

You need to pass the incremented value to the function itself instead of reading the printed value.

See the comments on the code.

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

    counters.forEach(counter => {
  
        /* expecting a parameter */
        const updateCount = (count) => {
            const target = +counter.getAttribute('data-target');

            /* at the first function call without argument 
             * read the innerText value, otherwise read it from
             * the argument (change const with var)
             */
            var count = count || +counter.innerText;

            const speed = target / 1000;
            
            
            if (count < target) {

                /* print the value with parseInt() so you keep
                 * the integer part only
                 */
                counter.innerText = parseInt(count + speed, 10);

                /* call the function passing the original sum of
                 * count + speed
                 */
                setTimeout(function() {
                  updateCount(count+speed)
                }, 1);

            } 
            else {
                /* use parseInt() */
                counter.innerText = parseInt(target, 10);
            }
        }
        updateCount();
    });
  
.counter {
  font-size: 20px;
  font-weight: bold;
  padding: 5px;
  display: block;
}
<span data-target="20" class="counter">0</span>
<span data-target="100" class="counter">0</span>
<span data-target="50" class="counter">0</span>
<span data-target="250000" class="counter">0</span>

As a side note, you can also pass a second target argument to the function, instead of continuously access the DOM to read it, exactly as I've done for count, since it doesn't change.

Upvotes: 1

G-Cyrillus
G-Cyrillus

Reputation: 106038

This might help you : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed

example with an extra data-attribute:

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

counters.forEach(counter => {
  const updateCount = () => {
    const target = +counter.getAttribute('data-target');
    const count = +counter.getAttribute('data-count');

    const speed = target / 1000; //this will not work accurate if using Math.ceil or floor to round number up

    if (count < target) {
      counter.setAttribute('data-count', count + speed);
      let stripped = (count + speed).toFixed(0);
      counter.innerText = stripped
      setTimeout(updateCount, 1);
    } else {
      counter.innerText = target;
    }
  }
  updateCount();
});
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html,
body {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  flex-direction: column;
  align-items: center;
}


/* HEADER */

.counter {
  font-size: 20px;
  font-weight: bold;
  padding: 5px;
}
<span data-target="20" class="counter">0</span>
<span data-target="100" data-count="0" class="counter">0</span>
<span data-target="50" data-count="0" class="counter">0</span>
<span data-target="250000" data-count="0" class="counter">0</span>

Upvotes: 0

Arianne
Arianne

Reputation: 507

The same value is being used for the calculation and displaying the value so if you round it off you lose the precision. If on the first iteration it does 1.3+3=4.3 on the next iteration the 4.3 will be rounded up and the sum would be 5+3. That's what's making the speed seem off.

Adding another hidden attribute to the span (count-value in the code below) gives you a way to store the whole number with all the decimals to use in the calculations and then you can display the number without decimals to the users without it throwing off the sum.

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

    counters.forEach(counter => {
        const updateCount = () => {
            const target =+counter.getAttribute('data-target');
            //update count with the full value
            const count =+counter.getAttribute('count-value');

            const speed = target / 1000; //this will not work accurate if using Math.ceil or floor to round number up
           
            if (count < target) {
                //set the innertext to the rounded up value
                counter.innerText = Math.ceil(count + speed);
                //update the hidden attribute with the full value
                counter.setAttribute('count-value', count + speed);
                setTimeout(updateCount,1);
            } else {
                counter.innerText = target;
            }
        }
        updateCount();
    });
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}
html , body {
  width: 100%;height: 100%;
  display: flex;
  justify-content: center;
  flex-direction: column;
  align-items: center;
}
/* HEADER */

.counter {
  font-size: 20px;
  font-weight: bold;
  padding: 5px;
}
    <span data-target="20" count-value="0" class="counter">0</span>
    <span data-target="100" count-value="0" class="counter">0</span>
    <span data-target="50" count-value="0" class="counter">0</span>
    <span data-target="250000" count-value="0" class="counter">0</span>

Upvotes: 0

Related Questions