5

I want to create an animation for an element with following properties:

  • it animates the element when it enters the viewport
  • if the element left the viewport and then enters it again it should be animated again
  • depending on the scroll direction/intersected side (from top or bottom) the animation should be different

For this purpose I use an IntersectionObserver and I came close to the desired outcome.
The only problem I am facing is, when I translate the element in the scroll direction (which is in this case transform: translateY) during the animation. This will cause the IntersectionObserver to trigger multiple or even infinite times.

enter image description here

function isIntersectingFromTop(entry){
  return entry.boundingClientRect.bottom != entry.intersectionRect.bottom;
} 

function isIntersectingFromBottom(entry){
  return entry.boundingClientRect.top != entry.intersectionRect.top;
}

var count = 0;

function incrementCounter(entry){
  document.querySelector(".counter").textContent += "intersectionRation (" + count + "): " + entry.intersectionRatio + "\n";
  count++;
}

let observer = new IntersectionObserver(
function (entries, observer) { 
  entries.forEach(function(entry){
    incrementCounter(entry)
    if (entry.isIntersecting) {
       if(isIntersectingFromTop(entry)){
         entry.target.classList.add("animated--up-in");
       } else if(isIntersectingFromBottom(entry)) {
         entry.target.classList.add("animated--down-in")
       }
    } else { 
      /** element is not in viewport anymore
        * this will be triggered right after the animation starts
        * since the translate is moving the elment out of the view
        * which is causing a new intersection (isIntersecting = false)
        */
      entry.target.classList.remove("animated--up-in");
      entry.target.classList.remove("animated--down-in");
    }
  });
});

observer.observe(document.querySelector(".to-animate"));
.container {
  height: 1000px;
  width: 100%;
}

.box{
  position: static;
  width: 100px;
  height: 100px;
  background: red;
  margin-top: 10px;
}

.to-animate{
  background: blue;
  opacity: 0;
}

.animated--up-in {
  animation: animateUpIn 1.5s forwards ease;
}

.animated--down-in {
  animation: animateDownIn 1.5s forwards ease;
}

@keyframes animateUpIn {
  from {
    transform: translateY(100px);
    opacity: 0;
  }
  to {
    transform: translateY(0);
    opacity: 1;
  }
}

@keyframes animateDownIn {
  from {
    transform: translateY(-100px);
    opacity: 0;
  }
  to {
    transform: translateY(0);
    opacity: 1;
  }
}



.counter {
  position: fixed;
  top: 10%;
  left: 30%;
  color: black;
  height: 80%;
  width: 50%;
  overflow-y: auto;
  padding: 10px;
}
<div class="container">
  <pre class="counter"></pre>
  <div class="box"></div>
  <div class="box"></div>
  <div class="box"></div>
  <div class="box"></div>
  <div class="box"></div>
  <div class="box"></div>
  <div class="box"></div>
  <div class="box to-animate"></div>
  <div class="box"></div>
  <div class="box"></div>
  <div class="box"></div>
  <div class="box"></div>
  <div class="box"></div>
  <div class="box"></div>
  <div class="box"></div>
  <div class="box"></div>
</div>

Question

How can I "tell" the IntersectionObserver to ignore translated position and just use the initial/original position to calculate intersections (with no extra element)? Is this even possible?

ysfaran
  • 5,189
  • 3
  • 21
  • 51

2 Answers2

2

I think that in this case you need to track the position on the screen of the stationary containers in which the animated elements will be nested.

Nikita
  • 21
  • 2
1

I have a similar problem with translateX on mobile device Animation has translateX(-100% or 100%);

threshold: 0.1

Block has width 100%, but it should work with a visibility of 10% (threshold 0.1), therefore it cannot work, the animation switches back and forth, endlessly.

If it's 90%, then it will work

aprinciple
  • 704
  • 5
  • 8