1

I have a section which showcases statistics.

  • Some of these statistics are numbers (i.e. 145);
  • Some are numbers, characters and symbols (i.e. 65K+ or $20.00)
  • Some are just purely text (i.e. "text")

When this section is in view, I want stats which contain a number to count up (and naturally stats that don't contain numbers to not count up).

The effect I'm trying to achieve is:

  • All .statsBannerCard's are set to visibility: hidden
  • User scrolls to section
  • JS checks if first .statsBannerCard contains number; if yes, counts up (this single card is visibility: visible now).
  • Then once the counter for the first card is complete, make the second card visible and check if it contains a number, and so on.

The proceeding card is essentially shown once the previous card counter is complete. If a card just contains text (so we can't count up), it will just show the card and move on.

Current issue:

In my demo below, I'm using the data-number attribute to determine what number the card needs to count up to. When scrolling down, the first counter works (because it is a pure integer), however, it stops working when characters, symbols or letters are involved.

Demo:

$(function() {

  gsap.registerPlugin(ScrollTrigger);


  $(".statsBannerCard__statistic").each(function(index, element) {
    var count = $(this),
      zero = {
        val: 0
      },
      num = count.data("number"),
      split = (num + "").split("."), // to cover for instances of decimals
      decimals = split.length > 1 ? split[1].length : 0;

    gsap.to(zero, {
      val: num,
      duration: 2,
      scrollTrigger: element,
      onUpdate: function() {
        count.text(zero.val.toFixed(decimals));
      }
    });
  });

});
.spacer{
  height: 100vh;
  background: lightblue;
}

.statsBanner{
  background: #F283D6;
  padding: 100px 0;
}

.statsBanner__intro{
  margin-bottom: 60px;
}

.statsBannerCard{
  /* visibility: hidden; */
  
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.0/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.0/ScrollTrigger.min.js"></script>

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet">


<section class="spacer">
  Scroll down
</section>


<section class="statsBanner">
  <div class="container">

    <div class="row">
      <div class="col-12">
        <div class="statsBanner__intro text-center">
          <h2>Start counter when this section is in view.</h2>
        </div>
      </div>
    </div>

    <div class="row justify-content-evenly">


      <div class="col-12 col-sm-3">
        <div class="statsBannerCard text-center">
          <span class="statsBannerCard__statistic" data-number="145">145</span>
        </div>
      </div>

      <div class="col-12 col-sm-3">
        <div class="statsBannerCard text-center">
          <span class="statsBannerCard__statistic" data-number="Text">Text</span>
        </div>
      </div>

      <div class="col-12 col-sm-3">
        <div class="statsBannerCard text-center">
          <span class="statsBannerCard__statistic" data-number="$20,000">$20,000</span>
        </div>
      </div>

      <div class="col-12 col-sm-3">
        <div class="statsBannerCard text-center">
          <span class="statsBannerCard__statistic" data-number="60K+">60K+</span>
        </div>
      </div>



    </div>
  </div>
</section>
Freddy
  • 683
  • 4
  • 35
  • 114

2 Answers2

1

You need to use recursive function to process the stats one by one. Use onComplete function call to process next stat. Explanation is in code comments:

$(function() {

  gsap.registerPlugin(ScrollTrigger);

  //get stats in array to process one by one
  let stats = $(".statsBannerCard__statistic").toArray();

  //recursive function
  function countOne(stats) {
    if (stats.length < 1) { //when all stats done exit
      return;
    }
    let stat = stats.shift(); //remove first

    //make the card visible
    $(stat).parent().css({
      visibility: 'visible'
    });

    var count = $(stat),
      zero = {
        val: 0
      },
      num = count.data("number"),
      split = (num + "").split("."), // to cover for instances of decimals
      decimals = split.length > 1 ? split[1].length : 0;

    //if it's not a number then skip counting
    if (typeof num == 'number') {
      gsap.to(zero, {
        val: num,
        duration: 2,
        scrollTrigger: stat,
        onUpdate: function() {
          let numText = zero.val.toFixed(decimals)
          numText = numText.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,')
          count.text(numText);
        },
        onComplete: function() {
          countOne(stats);
        }
      });

    } else {
      count.text(num);
      countOne(stats);
    }
  }

  //initiate
  countOne(stats);
});
.spacer {
  height: 100vh;
  background: lightblue;
}

.statsBanner {
  background: #F283D6;
  padding: 50px 0;
}

.statsBanner__intro {
  margin-bottom: 60px;
}

.statsBannerCard {
  visibility: hidden;
  font-size: 2rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.0/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.0/ScrollTrigger.min.js"></script>

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet">


<section class="spacer">
  Scroll down
</section>


<section class="statsBanner">
  <div class="container">

    <div class="row">
      <div class="col-12">
        <div class="statsBanner__intro text-center">
          <h2>Start counter when this section is in view.</h2>
        </div>
      </div>
    </div>

    <div class="row justify-content-evenly">

      <div class="col-12 col-sm-3">
        <div class="statsBannerCard text-center">
          <span class="statsBannerCard__statistic" data-number="145">145</span>
        </div>
      </div>

      <div class="col-12 col-sm-3">
        <div class="statsBannerCard text-center">
          <span class="statsBannerCard__statistic" data-number="Text">Text</span>
        </div>
      </div>

      <div class="col-12 col-sm-3">
        <div class="statsBannerCard text-center">
          $<span class="statsBannerCard__statistic" data-number="20000">20,000</span>
        </div>
      </div>

      <div class="col-12 col-sm-3">
        <div class="statsBannerCard text-center">
          <span class="statsBannerCard__statistic" data-number="60">60</span>K+
        </div>
      </div>

    </div>
  </div>
</section>


To properly handle currencies you'll have to use some library or special implementation. Here, I've cut corners for the demo.
Refer how to convert currency string to double
And how to format number to currency

the Hutt
  • 16,980
  • 2
  • 14
  • 44
0

Pure JS solution(but without counting animation).

The function is triggered when element comes into view. Can view in detail here

For counting animation you can refer to this answer

let statistic = document.querySelectorAll(".statsBannerCard__statistic");
let valueArr = [];
let trueOnlyOnce = true;
let i = 0;

statistic.forEach(e => valueArr.push(e.dataset.number))

let statsBanner = document.querySelector(".statsBanner");
document.addEventListener('scroll', animationTrigger);

function animationTrigger() {
  if (window.scrollY >= statsBanner.scrollHeight - window.innerHeight && trueOnlyOnce) {
    trueOnlyOnce = false;
    animationCounter();
  }
}

function animationCounter() {
  if (i < statistic.length) {
    if (valueArr[i].replace(/[^\d]/g, '') !== "") {
      statistic[i].innerHTML = "0";
      statistic[i].style.visibility = "visible";
      setTimeout(function() {
        statistic[i].innerHTML = valueArr[i];
        i = i + 1;
        animationCounter();
      }, 2000)
    } else if (valueArr[i].replace(/[^\d]/g, '') == "") {
      statistic[i].style.visibility = "visible";
      i = i + 1;
      animationCounter();
    }
  }
}
.spacer {
  height: 100vh;
  background: lightblue;
}

.statsBanner {
  background: #F283D6;
  padding: 100px 0;
}

.statsBanner__intro {
  margin-bottom: 60px;
}

.statsBannerCard {
  visibility: hidden;
}
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet">


<section class="spacer">
  Scroll down
</section>


<section class="statsBanner">
  <div class="container">

    <div class="row">
      <div class="col-12">
        <div class="statsBanner__intro text-center">
          <h2>Start counter when this section is in view.</h2>
        </div>
      </div>
    </div>

    <div class="row justify-content-evenly">


      <div class="col-12 col-sm-3">
        <div class="statsBannerCard text-center">
          <span class="statsBannerCard__statistic" data-number="145">145</span>
        </div>
      </div>

      <div class="col-12 col-sm-3">
        <div class="statsBannerCard text-center">
          <span class="statsBannerCard__statistic" data-number="Text">Text</span>
        </div>
      </div>

      <div class="col-12 col-sm-3">
        <div class="statsBannerCard text-center">
          <span class="statsBannerCard__statistic" data-number="$20,000">$20,000</span>
        </div>
      </div>

      <div class="col-12 col-sm-3">
        <div class="statsBannerCard text-center">
          <span class="statsBannerCard__statistic" data-number="60K+">60K+</span>
        </div>
      </div>



    </div>
  </div>
</section>
Rana
  • 2,500
  • 2
  • 7
  • 28