Preface

Goal: Trigger animation on scroll using intersection observer.

Instead of using ready made animation, we can be brave enough to make our own custom animation. Such as this two cases.

  1. Javascript only, no stylesheet.
  2. Animation in stylesheet.

1: Numerator

Javascript only, no stylesheet.

Original Source

I have seen jquery numerator, and I want the native one. Luckily I found this one.

I use this source code, with a simple modification, to suit my need.

HTML Head

Add this javascript in the HTML head.

  <script src="js-75/numerator.js"></script>

HTML: Bulma: Level

I’m using layout level from Bulma.

With additional dataset for counter number, that could be read from javascript.

<nav class="level">
  <div class="level-item has-text-centered">
    <div>
      <p class="heading">Code</p>
      <p class="title numerator"
         data-number="8"
        >0</p>
    </div>
  </div>
  <div ...>
  <div ...>
  <div ...>
  <div ...>
</nav>

Javascript Skeleton

Consider adapt the javascript above:

document.addEventListener(
  "DOMContentLoaded", function(event) { 

  const ...

  let counterObserver = 
    new IntersectionObserver( (entries) => {
      entries.forEach((entry) => {
        if (entry.intersectionRatio > 0) {
          const ...
          const animate = () => {...}
          animate();
        }
    });
  });

  countersToObserve.forEach((element) => {
    counterObserver.observe(element);
  });

});

I know this is a long script.

Javascript: Variable Initialization

  const countersToObserve = document
    .querySelectorAll('.numerator');
  const stepfactor = 20;
  const timeout    = Math.ceil(1000/stepfactor);

Javascript: Scroll Event

If the element is happened to be displayed, we animate from zero to counter number.

if (entry.intersectionRatio > 0) {
  const counter = entry.target;
  const maxnum  = +counter.dataset.number;

  // reset on scroll
  counter.innerText = 0;

  const animate   = () => {...}

  animate();
}

Javascript: Animate Counter

And finally the numerator animation itself

  const animate   = () => {
    const current = +counter.innerText;
    const append  = (maxnum-current) / stepfactor;

    if (current < maxnum) {
      newcurrent = Math.ceil(current + append);
      counter.innerText = newcurrent;
      setTimeout(animate, timeout);
    } else {
      counter.innerText = maxnum;
    }
  }

It is home made. And I open for better enhancement.

Preview

Enjoy the result.


2: Progressbar

Animation in stylesheet.

HTML Head

Add both, stylesheet and javascript in the HTML head.

  <link rel="stylesheet" type="text/css" href="css-76/progressbar.css">
  <script src="js-76/progressbar.js"></script>

HTML: Custom Progress Bar

Franken Component

I don’t feel like bulma progressbar suit my need, so I grab other html code from w3school, but with modification. And I also grab the stylesheet, from somewhere in the internet. But I wrote the javascript myself. I know it is rather a frankenstein, but it works, at least for me.

  <div id="container__bar"
       class="has-text-left"
       data-count="114">

    <div class="my__progressbar"
       data-count="8">
      <div class="my__progressbar_text"
        >Code: <span class="my__progressbar_perc"
          ></span></div>
      <div class="my__inner_bar"></div>
    </div>

    <div ...>

    <div ...>

    <div ...>

    <div ...>

  </div>

Note that I also set the total count. Sum of all element, on the first place.

Stylesheet: Progress Bar Animation

The keyframes is very simple.

@keyframes my__progress__frames {
  from { width: 0%; }
  to   { width: 100%; }
}

I’m using material color gradient for background.

.my__inner_bar {
  background-color: #BBDEFB;
  background-image: linear-gradient(
        45deg, #2196F3 10px, 
        transparent 20px, transparent 25%, 
        #64B5F6 50%, #90CAF9 75%,
        transparent 75%, transparent);
}

The animation is

.my__inner_bar {
  animation-duration: 5s;
  animation-fill-mode: both;
  animation-name: my__progress__frames;
  animation-iteration-count: 1;
  animation-timing-function: ease;
  animation-direction: normal;
}

And additional cosmetics.

.my__progressbar_text {
  white-space: nowrap;
}

.my__inner_bar {
  height: 10px;
  border-radius: 10px;
}

Javascript Skeleton

We need to make custom javascript:

document.addEventListener(
  "DOMContentLoaded", function(event) { 

  ...

  let containerBarObserver = 
    new IntersectionObserver( (entries) => {
      entries.forEach((entry) => {
        ...
    });
  });

  containerBarObserver.observe(containerBar);
});

I know this is script is longer.

Javascript: Variable Initialization

  const progressbars = document
    .querySelectorAll(".my__progressbar");
  const myBars       = document
    .querySelectorAll(".my__inner_bar");
  const containerBar = document
    .getElementById("container__bar");
  const total = containerBar.dataset.count;

Javascript: Scroll Event

If the element is happened to be displayed, we reanimate each progressbar. The point is just toggling, the presence of each my__inner_bar class, as the page scrolled.

new IntersectionObserver( (entries) => {
  entries.forEach((entry) => {
    if (entry.intersectionRatio > 0) {
      // reset on scroll
      myBars.forEach((element) => {
        element.classList.add('my__inner_bar');
      });

      progressbars.forEach((element) => {...});
    } else {
      myBars.forEach((element) => {
        element.classList.remove('my__inner_bar');
      });
    }
});

Javascript: Animate Counter

And finally the numerator animation itself.

progressbars.forEach((element) => {
  const count = element.dataset.count;
  const width = Math.round((count/total)*100);
  element.style.width = width + "%";

  const percText = element
   .getElementsByClassName("my__progressbar_perc")[0]
   percText.innerText = width + "%";
});

Again, it is home made. And I open for better enhancement.

Preview

Enjoy the result.


Conclusion

What do you think?

Thank you for reading.

Farewell. We shall meet again.