Sarah
Sarah

Reputation: 61

Implementing a JS Countup Effect on a Website

I would like to display numbers that count up as soon as the viewer scrolls down to look at that area of the screen. The goal is to make the numbers count up consecutively until they reach the desired amount. My question is how to do that with the existing code provided.

    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'));
.statistics {
        padding-top: 30px;
        padding-bottom: 30px;
}

.statistics h1 {
        letter-spacing: 1.0px;
        font-family: 'Lora', serif;
        font-weight: 500;
        text-align: center;
        font-size: 5em;
        color: #39CEAF;
}
     
#value {
       font-size: 100px;
       font-color: blue;
}
    <div id="statistics" class="statistics container-fluid">
      <div class="col-md-4">
        <h1 id="value1">100+</h1>
        <h2>People receiving opportunities in the first 36 hours</h2>
      </div>
      <div class="col-md-4">
        <h1 id="value2">400+</h1>
        <h2>People receiving opportunities in the first month</h2>
      </div>
      <div class="col-md-4">
        <h1 id="value3">800+</h1>
        <h2>People receiving opportunities currently</h2>
      </div>
    </div>

Upvotes: 0

Views: 283

Answers (2)

Spectric
Spectric

Reputation: 31992

Add an event listener for scroll, check if the element is visible to the client, and if so, call the animateValue function. To prevent it from animating again and again, we can keep track with a custom data attribute (data-animated).

function animateValue(obj, start = 0, end = null, duration = 3000) {
  if (obj) {
    var textStarting = obj.innerHTML;
    end = end || parseInt(textStarting.replace(/\D/g, ""));
    var range = end - start;
    var minTimer = 50;
    var stepTime = Math.abs(Math.floor(duration / range));
    stepTime = Math.max(stepTime, minTimer);
    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 = textStarting.replace(/([0-9]+)/g, value);
      if (value == end) {
        clearInterval(timer);
      }
    }
    timer = setInterval(run, stepTime);
    run();
  }
}
const toAnimate = document.querySelectorAll('h1');
function checkElements() {
  toAnimate.forEach(function(e) {
    if (isVisible(e) && e.getAttribute('data-animated') != "true") {
      animateValue(e);
      e.setAttribute('data-animated', 'true');
    }
  })
}
document.addEventListener("scroll", checkElements);
checkElements();

function isVisible(el) {
  /* https://stackoverflow.com/a/7557433/14251221 */
  var rect = el.getBoundingClientRect();
  return (
    rect.top >= 0 &&
    rect.left >= 0 &&
    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
    rect.right <= (window.innerWidth || document.documentElement.clientWidth)
  );
}
.statistics {
  padding-top: 30px;
  padding-bottom: 30px;
}

.statistics h1 {
  letter-spacing: 1.0px;
  font-family: 'Lora', serif;
  font-weight: 500;
  text-align: center;
  font-size: 5em;
  color: #39CEAF;
}

#value {
  font-size: 100px;
  font-color: blue;
}
<div id="statistics" class="statistics container-fluid">
  <div class="col-md-4">
    <h1 id="value1" data-animated="false">100+</h1>
    <h2>People receiving opportunities in the first 36 hours</h2>
  </div>
  <div class="col-md-4">
    <h1 id="value2" data-animated="false">400+</h1>
    <h2>People receiving opportunities in the first month</h2>
  </div>
  <div class="col-md-4">
    <h1 id="value3" data-animated="false">800+</h1>
    <h2>People receiving opportunities currently</h2>
  </div>
</div>

Upvotes: 1

Scott Marcus
Scott Marcus

Reputation: 65796

You're trying to run your code by passing it an element that doesn't exist (animateValue(document.getElementById('value'));). If I pass valid DOM references, it appears to work.

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('value1'));
     animateValue(document.getElementById('value2'));
     animateValue(document.getElementById('value3'));     
.statistics {
        padding-top: 30px;
        padding-bottom: 30px;
}

.statistics h1 {
        letter-spacing: 1.0px;
        font-family: 'Lora', serif;
        font-weight: 500;
        text-align: center;
        font-size: 5em;
        color: #39CEAF;
}
     
#value {
       font-size: 100px;
       font-color: blue;
}
<div id="statistics" class="statistics container-fluid">
      <div class="col-md-4">
        <h1 id="value1">100+</h1>
        <h2>People receiving opportunities in the first 36 hours</h2>
      </div>
      <div class="col-md-4">
        <h1 id="value2">400+</h1>
        <h2>People receiving opportunities in the first month</h2>
      </div>
      <div class="col-md-4">
        <h1 id="value3">800+</h1>
        <h2>People receiving opportunities currently</h2>
      </div>
    </div>

Upvotes: 1

Related Questions