Home > database >  How can I run a function when an element enters the viewport?
How can I run a function when an element enters the viewport?

Time:09-28

I have a counter stats, which works fine, but how can I write a condition that it should start only if the parent is in viewport? Maybe my syntax is wrong or I don't understand how to connect what I got. Any advice is appreciated.

This is what I got for now:

$(window).scroll(function() {
  if ($('.numbers-inner').isInViewport()) {
    //then what?;

  } else {
    //else what?;
  }
});
$('.magPub, .pplHired, .yrsExp').each(function() {
  $(this).prop('Counter', 0).animate({
    Counter: $(this).text()
  }, {
    duration: 1500,
    easing: 'swing',
    step: function(now) {
      $(this).text(Math.ceil(now));
    }
  });
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div class="numbers-inner stats">
  <ul>
    <li><span class="magPub">33708</span> </li>
    <li>magazines published</li>
  </ul>

  <ul>
    <li><span class="pplHired">247</span></li>
    <li>people hired</li>
  </ul>

  <ul>
    <li><span class="yrsExp">17</span></li>

    <li>years of experience</li>
  </ul>

</div>

CodePudding user response:

This will fire animation when your parent becomes visible and will animate only once.

let hasBeenAnimated = false;

$.fn.isInViewport = function () {
    let elementTop = $(this).offset().top;
    let elementBottom = elementTop   $(this).outerHeight();

    let viewportTop = $(window).scrollTop();
    let viewportBottom = viewportTop   window.innerHeight;

    return elementBottom > viewportTop && elementTop < viewportBottom;
  };

function animate() {
    $('.magPub, .pplHired, .yrsExp').each(function() {
        $(this).prop('Counter', 0).animate({
            Counter: $(this).text()
        }, {
            duration: 1500,
            easing: 'swing',
            step: function(now) {
                $(this).text(Math.ceil(now));
            }
        });
    });
}

$(window).scroll(function() {
    if ($('.numbers-inner').isInViewport() && !hasBeenAnimated) {
        hasBeenAnimated = true;
        animate();
    }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div style="height: 2000px">
  <h1>↓↓↓ SCROLL DOWN ↓↓↓</h1>
</div>
<div class="numbers-inner stats" style="background: red">
  <ul>
    <li><span class="magPub">33708</span> </li>
    <li>magazines published</li>
  </ul>

  <ul>
    <li><span class="pplHired">247</span></li>
    <li>people hired</li>
  </ul>

  <ul>
    <li><span class="yrsExp">17</span></li>

    <li>years of experience</li>
  </ul>

</div>

Please note that .isInViewport() is not a jQuery function, but rather a custom function the user has implemented in this question. Also notice that the original function has a bug, this is the proper implementation:

$.fn.isInViewport = function () {
  let elementTop = $(this).offset().top;
  let elementBottom = elementTop   $(this).outerHeight();

  let viewportTop = $(window).scrollTop();
  let viewportBottom = viewportTop   window.innerHeight;

  return elementBottom > viewportTop && elementTop < viewportBottom;
};

CodePudding user response:

JavaScript has an intersection observer API that would do just this. You setup the element to observe and it does the rest. In the below example we set the threshold to 50% (when 50% of your div shows, the function will be executed to begin the counting):

const target = document.querySelector('.numbers-inner.stats');

function handleIntersection(entries) {
  entries.map((entry) => {
    if (entry.isIntersecting) {
      $('.magPub, .pplHired, .yrsExp').each(function() {
        $(this).prop('Counter', 0).animate({
          Counter: $(this).text()
        }, {
          duration: 1500,
          easing: 'swing',
          step: function(now) {
            $(this).text(Math.ceil(now));
          }
        });
      });
    }
  });
}

let opts = {
  threshold: 0.5
}

const observer = new IntersectionObserver(handleIntersection, opts);
observer.observe(target);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div class="numbers-inner stats">
  <ul>
    <li><span class="magPub">33708</span> </li>
    <li>magazines published</li>
  </ul>

  <ul>
    <li><span class="pplHired">247</span></li>
    <li>people hired</li>
  </ul>

  <ul>
    <li><span class="yrsExp">17</span></li>

    <li>years of experience</li>
  </ul>

</div>

  • Related