Search code examples
javascripthtmlcsscounter

How to append a string onto innerText of a Number in JavaScript for an animated counter


enter image description here

I've built a ticker/counter animation with basic HTML, CSS and JavaScript and it is working well. This is for a website to show how performance and cashflow has improved when using a service so where I currently have it working and counting up to my target number, I want to append a string onto the end of these targets at the end so instead of (for example), the final target showing 35, I want it to show '35%' or for another element, '2.5x'.

I essentially want to append either a '%' onto the end if its a whole number, and if its a decimal, I want to append a 'x'.

The problem I'm having is that when I try at the end to append one of these strings onto my counter, it ruins everything and just keeps returning 0.01% instead of my target number which is 35, 70, 20 etc.

If I try to add counter.innerText = Math.ceil(count + inc) + '%' for instance, it breaks everything! I've tried to add .toString() to the end too, and then append the '%' but again the same.

These are my files, I'm sure I'm making a silly mistake somewhere so any help would be greatly appreciated!

HTML:

<div class="ticker-container">
  <h1 class='ticker-header'>The Superpowers of Inclusion & Diversity are clear</h1>
</div>

<section class='counters'>
  <div class='counter-container'>
    <div class='num-container'>
      <div class="counter" data-target='35'>0</div>
      <h4 class='count-subheader'>Better financial performance</h4>
      <p class='tick-p'>McKinsey</p>
    </div>
    <div class='num-container'>
      <div class="counter" data-target='70'>0</div>
      <h4 class='count-subheader'>More revenue from innovation</h4>
      <p class='tick-p'>BCG</p>
    </div>
    <div class='num-container'>
      <div class="counter" data-target='20'>0</div>
      <h4 class='count-subheader'>Higher revenue</h4>
      <p class='tick-p'>BCG</p>
    </div>
    <div class='num-container'>
      <div class="counter" data-target='2.5'>0</div>
      <h4 class='count-subheader'>Higher cashflow per employee</h4>
      <p class='tick-p'>HBR</p>
    </div>
  </div>
  </section>

CSS:

@import url('https://fonts.googleapis.com/css2?family=Rubik:wght@400;500;700;');

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: 'Roboto', sans-serif;
  background-image: linear-gradient(to right, #923CC7 , #B978E1);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  height: 100vh;
}

.counter-container {
  max-width: 1200px;
  margin: 0 auto;
  overflow: auto;
}

.ticker-container {
  flex: 1;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.ticker-header {
  font-size: 48px;
  margin-top: 20px;
}

.counters {
  padding: 50px 20px;
}

.counter-container {
  display: grid;
  grid-gap: 40px;
  grid-template-columns: repeat(4, 1fr);
  text-align: center;
}

.counter {
  font-size: 55px;
  margin: 10px 0;
  background-color: white;
  border-radius: 50%;
  width: 120px;
  height: 120px;
  display: flex;
  justify-content: center;
  align-items: center;
}

.count-subheader {
  font-size: 17px;
  margin-top: 25px;
}

.tick-p {
  color: white;
  margin-top: 20px;
}


.num-container {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

@media(max-width: 700px) {
  .counter-container {
    grid-template-columns: repeat(2, 1fr);
  }
}

JavaScript:

const counters = document.querySelectorAll('.counter');
const speed = 200;
const decSpeed = 30;

counters.forEach(counter => {
  const updateCount = () => {
    const target = +counter.getAttribute('data-target');
    const count = +counter.innerText;
    
    const inc = target / speed;
    
    const decInc = target / decSpeed;
    
    if (count < target && target % 1 === 0) {
      counter.innerText = Math.ceil(count + inc)
      setTimeout(updateCount, 1);
      
    } else if (count < target && target % 1 !== 0) {
      counter.innerText = (count + decInc).toFixed(1)
      setTimeout(updateCount, 1);
    } else {
      count.innerText = target;
    }
  }
  
  
  updateCount();
})


Solution

  • The problem is this line:

    const count = +counter.innerText;
    

    once you add a symbol to the innerText, next time this won't return a proper number (NaN).

    You can fix it by removing the symbol part before casting to number:

    const count = +counter.innerText.slice(0, -1);
    

    const counters = document.querySelectorAll('.counter');
    const speed = 200;
    const decSpeed = 30;
    
    counters.forEach(counter => {
      const updateCount = () => {
        const target = +counter.getAttribute('data-target');
        const count = +counter.innerText.slice(0, -1);
    
        const inc = target / speed;
    
        const decInc = target / decSpeed;
    
        if (count < target && target % 1 === 0) {
          counter.innerText = Math.ceil(count + inc) + '%'
          setTimeout(updateCount, 1);
    
        } else if (count < target && target % 1 !== 0) {
          counter.innerText = (count + decInc).toFixed(1) + 'x';
          setTimeout(updateCount, 1);
        } else {
          count.innerText = target;
        }
      }
    
      updateCount();
    })
    @import url('https://fonts.googleapis.com/css2?family=Rubik:wght@400;500;700;');
    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
    }
    
    body {
      font-family: 'Roboto', sans-serif;
      background-image: linear-gradient(to right, #923CC7, #B978E1);
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      height: 100vh;
    }
    
    .counter-container {
      max-width: 1200px;
      margin: 0 auto;
      overflow: auto;
    }
    
    .ticker-container {
      flex: 1;
      height: 100%;
      display: flex;
      align-items: center;
      justify-content: center;
    }
    
    .ticker-header {
      font-size: 48px;
      margin-top: 20px;
    }
    
    .counters {
      padding: 50px 20px;
    }
    
    .counter-container {
      display: grid;
      grid-gap: 40px;
      grid-template-columns: repeat(4, 1fr);
      text-align: center;
    }
    
    .counter {
      font-size: 55px;
      margin: 10px 0;
      background-color: white;
      border-radius: 50%;
      width: 120px;
      height: 120px;
      display: flex;
      justify-content: center;
      align-items: center;
    }
    
    .count-subheader {
      font-size: 17px;
      margin-top: 25px;
    }
    
    .tick-p {
      color: white;
      margin-top: 20px;
    }
    
    .num-container {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
    }
    
    @media(max-width: 700px) {
      .counter-container {
        grid-template-columns: repeat(2, 1fr);
      }
    }
    <div class="ticker-container">
      <h1 class='ticker-header'>The Superpowers of Inclusion & Diversity are clear</h1>
    </div>
    
    <section class='counters'>
      <div class='counter-container'>
        <div class='num-container'>
          <div class="counter" data-target='35'>0</div>
          <h4 class='count-subheader'>Better financial performance</h4>
          <p class='tick-p'>McKinsey</p>
        </div>
        <div class='num-container'>
          <div class="counter" data-target='70'>0</div>
          <h4 class='count-subheader'>More revenue from innovation</h4>
          <p class='tick-p'>BCG</p>
        </div>
        <div class='num-container'>
          <div class="counter" data-target='20'>0</div>
          <h4 class='count-subheader'>Higher revenue</h4>
          <p class='tick-p'>BCG</p>
        </div>
        <div class='num-container'>
          <div class="counter" data-target='2.5'>0</div>
          <h4 class='count-subheader'>Higher cashflow per employee</h4>
          <p class='tick-p'>HBR</p>
        </div>
      </div>
    </section>