javascripthtmlcssscrollcss-animations

Make the element sticky untill the scroll driven animation is finished


Here's what I'm trying to achieve:

enter image description here

-when the elements become visible to the user, the scroll-driven rotate animation starts on them one-by-one,and all 3 elements become sticky (meaning that they do not disappear while the user is scrolling down) until the animation on the last one is finished. -when the animation is finished, the page returns to its normal behavior

Executable code:

This code doesn't make the elements sticky when needed.

document.addEventListener('DOMContentLoaded', function() {
  const elements = document.querySelectorAll('.animation');
  let delay = 0;

  const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        entry.target.style.animationDelay = `${delay}s`;
        entry.target.style.animationPlayState = 'running';
        console.log('running');

        // Add sticky class when the element is in view
        entry.target.classList.add('sticky');

        delay += 2.5; // Increase delay for the next element
        observer.unobserve(entry.target); // Stop observing once the animation is triggered
      }
    });
  }, {
    threshold: 0.5
  }); // Trigger when 50% of the element is in view

  elements.forEach(element => {
    console.log('paused');
    element.style.animationPlayState = 'paused'; // Pause animation until triggered
    observer.observe(element);

    // Remove sticky class after the animation ends
    element.addEventListener('animationend', () => {
      console.log('animationend')
      element.classList.remove('sticky');
    });
  });
});
.content {
  width: 75%;
  max-width: 800px;
  margin: 0 auto;
}

p,
h1 {
  font-family: Arial, Helvetica, sans-serif;
}

h1 {
  font-size: 3rem;
}

p {
  font-size: 1.5rem;
  line-height: 1.5;
}

.animation {
  view-timeline: --subjectReveal block;
  animation-timeline: --subjectReveal;
  width: 200px;
  height: 200px;
  background-color: bisque;
  animation-iteration-count: 1;
  animation-name: mymove;
  animation-duration: 3s;
  position: relative;
  /* Normal position */
}

.animation:nth-child(1) {
  animation-delay: calc(var(--subjectReveal) * 1);
}

.animation:nth-child(2) {
  animation-delay: calc(var(--subjectReveal) * 2);
}

.animation:nth-child(3) {
  animation-delay: calc(var(--subjectReveal) * 3);
}


/* Define the sticky behavior */

.sticky {
  position: sticky;
  top: 10px;
  /* Adjust top as needed */
  z-index: 10;
  /* Ensure it stays on top */
}

@keyframes mymove {
  0% {
    opacity: 1;
    transform: rotate(0deg);
  }
  50% {
    opacity: 1;
    transform: rotate(90deg);
  }
  100% {
    opacity: 1;
    transform: rotate(180deg);
  }
}




.as-console-wrapper { height:50px; }
Scroll this
<div style="height:1000px; min-height: 300px; max-width: 100%; display: flex; flex-direction: row; justify-content: space-between; align-items: center;">
  <div class="animation"></div>
  <div class="animation"></div>
  <div class="animation"></div>
</div>


Solution

  • Would you like to try this?

    I used CSS animation-range-start, It was in the code you provided..

    but it seems difficult to support it on Firefox and Safari because it's an experimental function.

    I've worked on the movements you want, so use the parts you can refer to!

    I hope it helps :)

    document.addEventListener('DOMContentLoaded', function() {
      const elementsContent = document.querySelector('.animation-content');
      const elements = document.querySelectorAll('.animation');
    
      const observer = new IntersectionObserver((entries) => {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
            elements.forEach((element) => {
              element.style.animationName = 'mymove'; // add animation to div
              element.style.animationPlayState = 'running'; // Start animation
            });
            observer.unobserve(entry.target); // Stop observing
          }
        });
      }, {
        threshold: 0.8
      }); // Trigger when 80% of the element is in view
    
      observer.observe(elementsContent);
    });
    html,
    body {
      margin: 0;
      padding: 2vw;
      background: #222;
    }
    
    .animation-wrap {
      height: 300vh;
      background: #fff;
    }
    
    .animation-content {
      display: flex;
      justify-content: space-around;
      align-items: center;
      height: 100vh;
      position: sticky;
      top: 0;
    }
    
    .animation {
      view-timeline: --subjectReveal block;
      animation-timeline: --subjectReveal;
      animation-range-start: entry 200%;
      height: 0; /* ignore this, just for square */
      width: 20vw; /* ignore this, just for square */
      padding-bottom:20vw; /* ignore this, just for square */
      background-color: bisque;
      animation-iteration-count: 1;
      animation-duration: 3s;
      animation-fill-mode: forwards;
      /*whenendanimation,holdstartstate*/
      animation-play-state: paused;
      /*pausedatfirst*/
      position: relative;
      /*Normalposition*/
    }
    
    .animation:nth-child(1) {
      animation-delay: 0s;
    }
    
    .animation:nth-child(2) {
      animation-delay: 1.4s;
    }
    
    .animation:nth-child(3) {
      animation-delay: 2.8s;
    }
    
    @keyframes mymove {
      0% {
        transform: rotate(0deg);
      }
      25% {
        transform: rotate(360deg);
      }
      100% {
        transform: rotate(360deg);
      }
    }
    
    div[class*="other-content"] {
      display: flex;
      align-items: center;
      justify-content: center;
      text-align: center;
      font-size: 3rem;
    }
    
    .other-content_01 {
      height: 400px;
      background: #eee;
    }
    
    .other-content_02 {
      height: 1000px;
      background: #ddd;
    }
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <link rel="stylesheet" href="test.css">
    </head>
    
    <body>
      <div class="other-content_01">
        Scroll Page :)
      </div>
      <div class="animation-wrap">
        <div class="animation-content">
          <div class="animation"></div>
          <div class="animation"></div>
          <div class="animation"></div>
        </div>
      </div>
      <div class="other-content_02">
        Thank you!
      </div>
    
      <script src="test.js"></script>
    </body>
    
    </html>