user11783441
user11783441

Reputation:

Animated counters on scroll not all loading

I have wrote the code below to get animated counters starting when visible on the window. It works well when the counters are all visible on the same row, but if only the first one is visible, this one will start the animation, but the others won't even if we scroll down. The first one is complete, but the others remain to zero.

/* SCROLL FUNCTIONS */

// Every time the window is scrolled... 
$(window).scroll(function() {

  // Check the location of each desired element
  $('.counter').each(function(i) {

    var bottom_of_object = $(this).offset().top + $(this).outerHeight();
    var bottom_of_window = $(window).scrollTop() + $(window).height();

    // If the object is completely visible in the window, fade it it
    if (bottom_of_window > bottom_of_object) {

      var $this = $(this);
      $({
        Counter: 0
      }).animate({
        Counter: $this.attr('data-to')
      }, {
        duration: 2000,
        easing: 'swing',
        step: function() {
          $this.text(Math.ceil(this.Counter));
        },
        complete() {
          $this.text(Math.ceil(this.Counter));
        }
      });

      $(window).off("scroll");

    }

  });

});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="row">
  <div class="col">
    <div class="row counters text-dark">
      <div class="col-sm-6 col-lg-3 mb-4 mb-lg-0">
        <div class="counter" data-to="30000">0</div>
        <label>Happy Clients</label>
      </div>
      <div class="col-sm-6 col-lg-3 mb-4 mb-lg-0">
        <div class="counter" data-to="15">0</div>
        <label>Years in Business</label>
      </div>
      <div class="col-sm-6 col-lg-3 mb-4 mb-sm-0">
        <div class="counter" data-to="352">0</div>
        <label>Cups of Coffee</label>
      </div>
      <div class="col-sm-6 col-lg-3">
        <div class="counter" data-to="178">0</div>
        <label>High Score</label>
      </div>
    </div>
  </div>
</div>

Upvotes: 0

Views: 207

Answers (4)

cloned
cloned

Reputation: 6742

Listening to scroll event is not performance friendly, you should really consider using Intersection Observer for stuff like this.

First you have to create a new observer:

var options = {
  rootMargin: '0px',
  threshold: 1.0
}

var observer = new IntersectionObserver(callback, options);

Here we define that once your target Element is 100% visible in the viewport (threshold of 1) your callback Function is getting executed. Here you can define another percentage, 0.5 would mean that the function would be executed once your element is 50% visible.

Then you have to define which elements to watch, in your case this would be the counter elements:

var target = document.querySelector('.counter');
observer.observe(target);

Last you need to specify what should happen once the element is visible in your viewport by defining the callback function:

var callback = function(entries, observer) { 
  entries.forEach(entry => {
    // Each entry describes an intersection change for one observed
    // here you animate the counter
  });
};

In your specific case you probably won't run into performance problems but if you have more and more elements you will start to notice something. So it's better to know of this and to "do it right" if you come across this problem again.

If you need to support older browsers, use the official polyfill from w3c.

You can also remove the observer from any element if you don't need element where

Upvotes: 0

user11783441
user11783441

Reputation:

/*
	SCROLL FUNCTIONS
	********************************/

	// Every time the window is scrolled... 
	$(window).scroll(function () {
        
    // Check the location of each desired element
		$('.count').each(function (i) {

			var bottom_of_object = $(this).offset().top + $(this).outerHeight();
			var bottom_of_window = $(window).scrollTop() + $(window).height();

			// If the object is completely visible in the window, fade it it
			if (bottom_of_window > bottom_of_object) {

				var $this = $(this);
				$({
					Counter: 0
				}).animate({
					Counter: $this.attr('data-to')
				}, {
					duration: 2000,
					easing: 'swing',
					step: function () {
						$this.text(Math.ceil(this.Counter));
					},
                    complete(){
                        $this.text(Math.ceil(this.Counter));
                    }
				});
                
                $(this).removeClass('count').addClass('counted');

			}

		}); 
        
	});
<div class="row">
    <div class="col">

        <div class="row counters text-dark">
            <div class="col-sm-6 col-lg-3 mb-4 mb-lg-0">
                <div class="count" data-to="30000">0</div>
                <label>Happy Clients</label>
            </div>
            <div class="col-sm-6 col-lg-3 mb-4 mb-lg-0">
                <div class="count" data-to="15">0</div>
                <label>Years in Business</label>
            </div>
            <div class="col-sm-6 col-lg-3 mb-4 mb-sm-0">
                <div class="count" data-to="352">0</div>
                <label>Cups of Coffee</label>
            </div>
            <div class="col-sm-6 col-lg-3">
                <div class="count" data-to="178">0</div>
                <label>High Score</label>
            </div>
        </div>

    </div>
</div>

Upvotes: 0

DanyAlejandro
DanyAlejandro

Reputation: 1468

The problem is this line of code:

$(window).off("scroll");

Your off call unbinds all events, not just one. That means all scroll event bindings are lost after the first number animation executes.

To solve this, you need to bind and unbind each number's animation separately. A simple way to do this would be to have a different function for each number animation and bind/unbind them separately. A generic example:

var myScroll1 = function () {
  $(window).off("scroll", myScroll1)
}
$(window).on("scroll", myScroll1)

Notice we are turning on and off just this specific function reference. You can have 4 of them and switch them on and off separately.

EDIT: Here's your script modified to work as explained:

var anim1 = function () { animateAndKill(1, $("#n1"), 3000, anim1); }
var anim2 = function () { animateAndKill(2, $("#n2"), 15,  anim2); }
var anim3 = function () { animateAndKill(3, $("#n3"), 352, anim3); }
var anim4 = function () { animateAndKill(4, $("#n4"), 178, anim4); }


// Every time the window is scrolled...
function animateAndKill(id, $number, max, myFunction) {
	var bottom_of_object = $number.offset().top + $number.outerHeight();
	var bottom_of_window = $(window).scrollTop() + window.innerHeight;
	
	// If the object is completely visible in the window, fade it it
	if (bottom_of_window > bottom_of_object) {
		$({ Counter: 0 }).animate({ Counter: max }, {
			duration: 2000,
			easing: 'swing',
			step: function () {
				var n = Math.ceil(this.Counter);
				$number.html(n);
			}
		});
		
		$(window).off("scroll", myFunction);
	}
}

$(window).on("scroll", anim1);
$(window).on("scroll", anim2);
$(window).on("scroll", anim3);
$(window).on("scroll", anim4);
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="code.js"></script>
</head>
<body>
<div style="height: 1000px; background: #33FF44"></div>
<div class="row" style="z-index: 100; font-size: 100px;">
  <div class="col">
    <div class="row counters text-dark">
      <div class="col-sm-6 col-lg-3 mb-4 mb-lg-0">
        <div id="n1" class="counter" data-to="30000">0</div>
        <label>Happy Clients</label>
      </div>
      <div class="col-sm-6 col-lg-3 mb-4 mb-lg-0">
        <div id="n2" class="counter" data-to="15">0</div>
        <label>Years in Business</label>
      </div>
      <div class="col-sm-6 col-lg-3 mb-4 mb-sm-0">
        <div id="n3" class="counter" data-to="352">0</div>
        <label>Cups of Coffee</label>
      </div>
      <div class="col-sm-6 col-lg-3">
        <div id="n4" class="counter" data-to="178">0</div>
        <label>High Score</label>
      </div>
    </div>
  </div>
</div>
<div style="height: 3000px; background: #33FF44"></div>
</body>
</html>

Upvotes: 1

Tyddlywink
Tyddlywink

Reputation: 891

https://jsfiddle.net/tyddlywink/pdvh4b3n/

Get rid of the $(window).off("scroll");bit. And keep track of who's already been counted or not.

<div class="row">
  <div class="col">
    <div class="row counters text-dark">
      <div class="col-sm-6 col-lg-3 mb-4 mb-lg-0">
        <div class="counter" data-to="30000" data-counted='false'>0</div>
        <label>Happy Clients</label>
      </div>
      <div style="height: 750px">

      </div>
      <div class="col-sm-6 col-lg-3 mb-4 mb-lg-0">
        <div class="counter" data-to="15" data-counted='false'>0</div>
        <label>Years in Business</label>
      </div>
     <div style="height: 750px">

      </div>
      <div class="col-sm-6 col-lg-3 mb-4 mb-sm-0">
        <div class="counter" data-to="352" data-counted='false'>0</div>
        <label>Cups of Coffee</label>
      </div>
      <div style="height: 750px">

      </div>
      <div class="col-sm-6 col-lg-3">
        <div class="counter" data-to="178" data-counted='false'>0</div>
        <label>High Score</label>
      </div>
    </div>
  </div>
</div>

Javascript:

// Every time the window is scrolled... 
$(window).scroll(function() {

  // Check the location of each desired element
  $('.counter').each(function(i) {

    var bottom_of_object = $(this).offset().top + $(this).outerHeight();
    var bottom_of_window = $(window).scrollTop() + $(window).height();
    var counted = $(this).data("counted");



    // If the object is completely visible in the window, fade it it
    if (!counted && bottom_of_window > bottom_of_object) {
      $(this).data("counted", true);
      var $this = $(this);
      $({
        Counter: 0
      }).animate({
        Counter: $this.attr('data-to')
      }, {
        duration: 2000,
        easing: 'swing',
        step: function() {
          $this.text(Math.ceil(this.Counter));
        },
        complete() {
          $this.text(Math.ceil(this.Counter));
        }
      });
    }
  });
});

Upvotes: 0

Related Questions