Search code examples
jquerycounterviewport

jquery counter activate on viewport


I have copied this counter code from https://bestjquery.com/tutorial/counter/demo155/ It works good, but when I put it at the bottom of page, it starts immediately and will finish counting before I scroll to view it. I want it start counting when it appears on viewport. Thank you. This is the code:

$(document).ready(function() {
  $('.counter-value').each(function() {
    $(this).prop('Counter', 0).animate({
      Counter: $(this).text()
    }, {
      duration: 3500,
      easing: 'swing',
      step: function(now) {
        $(this).text(Math.ceil(now));
      }
    });
  });
});
.counter {
  background: #fff;
  font-family: 'Noto Sans JP', sans-serif;
  text-align: center;
  width: 210px;
  padding: 0 0 25px;
  margin: 0 auto 15px;
  border-radius: 10px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
  position: relative;
}

.counter:before {
  content: "";
  background: #fff;
  width: 30px;
  height: 30px;
  border-radius: 5px 0;
  box-shadow: 4px 4px 4px rgba(0, 0, 0, 0.07);
  transform: translateX(-50%) rotate(45deg);
  position: absolute;
  bottom: -15px;
  left: 50%;
}

.counter .counter-value {
  color: #fff;
  background: linear-gradient(to right, #19bbd2, #2778ee);
  font-size: 38px;
  font-weight: 300;
  padding: 0 0 3px;
  margin: 0 0 25px;
  border-radius: 10px 10px 0 0;
  display: block;
}

.counter h3 {
  color: #2778ee;
  font-size: 18px;
  font-weight: 900;
  letter-spacing: 0.5px;
  text-transform: capitalize;
  margin: 0 0 25px;
}

.counter .counter-icon {
  color: #fff;
  background: linear-gradient(to right, #19bbd2, #2778ee);
  font-size: 40px;
  line-height: 60px;
  width: 65px;
  height: 65px;
  margin: 0 auto;
  border-radius: 10px;
}

.counter.purple .counter-value,
.counter.purple .counter-icon {
  background: linear-gradient(to right, #8f70e7, #c452ef);
}

.counter.purple h3 {
  color: #c452ef;
}

.counter.magenta .counter-value,
.counter.magenta .counter-icon {
  background: linear-gradient(to right, #e84a94, #ae379b);
}

.counter.magenta h3 {
  color: #ae379b;
}

.counter.yellow .counter-value,
.counter.yellow .counter-icon {
  background: linear-gradient(to right, #fecb4b, #e69814);
}

.counter.yellow h3 {
  color: #e69814;
}

@media screen and (max-width:990px) {
  .counter {
    margin-bottom: 45px;
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
<div class="container">
  <div class="row">
    <div class="col-md-3 col-sm-6">
      <div class="counter">
        <span class="counter-value">343</span>
        <h3>Web Designing</h3>
        <div class="counter-icon">
          <i class="fa fa-briefcase"></i>
        </div>
      </div>
    </div>
    <div class="col-md-3 col-sm-6">
      <div class="counter purple">
        <span class="counter-value">324</span>
        <h3>Web Development</h3>
        <div class="counter-icon">
          <i class="fa fa-globe"></i>
        </div>
      </div>
    </div>
  </div>
</div>


Solution

  • Try like below. Added description in comments.

    1. Add additional class pending in your span with class="counter-value" like <span class="counter-value pending">324</span>. This pending class is used to check whether animation is already started or not. If its already started then we don't want to re-animate it.
    2. Added new function setAnimation() in script.
    3. Used Utility Function to check whether element is visible or not. Referenced from How to check if element is visible after scrolling?.
    4. Add scroll event and call setAnimation() on scroll.

    setAnimation function.

    // create new function which will trigger your animation
    function setAnimation() {
      // add additional class pending to your .counter-value element to check whether animation is pending or not
      // retrieve only pending elements and loop over it
      $('.counter-value.pending').each(function() {
        // check if element is in view and visible
        var isElementInView = Utils.isElementInView($(this), false);    
        if (isElementInView) {
          // if visible then remove class pending so it won't animate multiple times
          $(this).removeClass('pending');
          // initialize animation
          $(this).prop('Counter', 0).animate({
            Counter: $(this).text()
          }, {
            duration: 3500,
            easing: 'swing',
            step: function(now) {
              $(this).text(Math.ceil(now));
            }
          });
        }
      });
    }
    

    Try entire code below. Note : Added <div class="container" style="height:200px;"></div> just for testing purpose only.

    $(document).ready(function() {
      // call newly created function
      setAnimation();
    });
    
    // add scroll event to check for animation on scroll
    $(document).scroll(function() {
      // call newly created function
      setAnimation();
    });
    
    // Create new function which will trigger your animation
    function setAnimation() {
      // add additional class pending to your .counter-value element to check whether animation is pending or not
      // retrieve only pending elements and loop over it
      $('.counter-value.pending').each(function() {
        // check if element is in view and visible
        var isElementInView = Utils.isElementInView($(this), false);    
        if (isElementInView) {
          // if visible then remove class pending so it won't animate multiple times
          $(this).removeClass('pending');
          // initialize animation
          $(this).prop('Counter', 0).animate({
            Counter: $(this).text()
          }, {
            duration: 3500,
            easing: 'swing',
            step: function(now) {
              $(this).text(Math.ceil(now));
            }
          });
        }
      });
    }
    
    // Added util code from https://stackoverflow.com/questions/487073/how-to-check-if-element-is-visible-after-scrolling
    function Utils() {
    
    }
    
    Utils.prototype = {
      constructor: Utils,
      isElementInView: function(element, fullyInView) {
        var pageTop = $(window).scrollTop();
        var pageBottom = pageTop + $(window).height();
        var elementTop = $(element).offset().top;
        var elementBottom = elementTop + $(element).height();
    
        if (fullyInView === true) {
          return ((pageTop < elementTop) && (pageBottom > elementBottom));
        } else {
          return ((elementTop <= pageBottom) && (elementBottom >= pageTop));
        }
      }
    };
    
    var Utils = new Utils();
    .counter {
      background: #fff;
      font-family: 'Noto Sans JP', sans-serif;
      text-align: center;
      width: 210px;
      padding: 0 0 25px;
      margin: 0 auto 15px;
      border-radius: 10px;
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
      position: relative;
    }
    
    .counter:before {
      content: "";
      background: #fff;
      width: 30px;
      height: 30px;
      border-radius: 5px 0;
      box-shadow: 4px 4px 4px rgba(0, 0, 0, 0.07);
      transform: translateX(-50%) rotate(45deg);
      position: absolute;
      bottom: -15px;
      left: 50%;
    }
    
    .counter .counter-value {
      color: #fff;
      background: linear-gradient(to right, #19bbd2, #2778ee);
      font-size: 38px;
      font-weight: 300;
      padding: 0 0 3px;
      margin: 0 0 25px;
      border-radius: 10px 10px 0 0;
      display: block;
    }
    
    .counter h3 {
      color: #2778ee;
      font-size: 18px;
      font-weight: 900;
      letter-spacing: 0.5px;
      text-transform: capitalize;
      margin: 0 0 25px;
    }
    
    .counter .counter-icon {
      color: #fff;
      background: linear-gradient(to right, #19bbd2, #2778ee);
      font-size: 40px;
      line-height: 60px;
      width: 65px;
      height: 65px;
      margin: 0 auto;
      border-radius: 10px;
    }
    
    .counter.purple .counter-value,
    .counter.purple .counter-icon {
      background: linear-gradient(to right, #8f70e7, #c452ef);
    }
    
    .counter.purple h3 {
      color: #c452ef;
    }
    
    .counter.magenta .counter-value,
    .counter.magenta .counter-icon {
      background: linear-gradient(to right, #e84a94, #ae379b);
    }
    
    .counter.magenta h3 {
      color: #ae379b;
    }
    
    .counter.yellow .counter-value,
    .counter.yellow .counter-icon {
      background: linear-gradient(to right, #fecb4b, #e69814);
    }
    
    .counter.yellow h3 {
      color: #e69814;
    }
    
    @media screen and (max-width:990px) {
      .counter {
        margin-bottom: 45px;
      }
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
    <div class="container" style="height:200px;"></div>
    <div class="container">
      <div class="row">
        <div class="col-md-3 col-sm-6">
          <div class="counter">
            <span class="counter-value pending">343</span>
            <h3>Web Designing</h3>
            <div class="counter-icon">
              <i class="fa fa-briefcase"></i>
            </div>
          </div>
        </div>
        <div class="col-md-3 col-sm-6">
          <div class="counter purple">
            <span class="counter-value pending">324</span>
            <h3>Web Development</h3>
            <div class="counter-icon">
              <i class="fa fa-globe"></i>
            </div>
          </div>
        </div>
      </div>
    </div>