Scrolltrigger Animation, GSAP, ELementor

Create stacked cards with floating animation in Elementor

SHARE

Get the custom CODE for my tutorial:

GSAP/Javascript Code:

<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script>

<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/ScrollTrigger.min.js"></script>

<script>
const cardsContainer = document.querySelector('.cards');
const cards = document.querySelectorAll('.card');

cards.forEach((card, index) => {
  const cardInner = card.querySelector('.card__inner');

  // Check if the card is the last in the array
  const isLastCard = index === cards.length - 1;

  // Random rotation between -5 and +5 degrees
  const rotation = gsap.utils.random(-5, 5, true);

  // Set initial values for scale and filter
  gsap.set(cardInner, {
    scale: 1, // Set initial scale
    filter: 'brightness(1)', // Set initial brightness
    rotation: rotation // Initial rotation
  });

  // First ScrollTrigger for scaling and rotation
  ScrollTrigger.create({
    trigger: card,
    start: 'top 100%', // Scaling starts when the card reaches the middle of the viewport
    end: 'top 25%', // Scaling ends when the card reaches 25% of the viewport
    scrub: 1, // Sync the animation with scrolling
    onUpdate: (self) => {
      const progress = self.progress;

      // Scale and rotate the card
      gsap.to(cardInner, {
        scale: gsap.utils.interpolate(1.2, 0.9, progress), // Scale from 1.2 to 0.9
        rotation: rotation, // Maintain the rotation
        overwrite: false // No overwrite to avoid conflicts
      });
    }
  });

  // Second ScrollTrigger for dimming the previous card
  ScrollTrigger.create({
    trigger: card,
    start: 'top 75%', // The new card dims the previous one when it reaches 75% of the viewport
    end: 'top 50%',   // Dimming ends when the top of the card reaches 50% of the viewport
    scrub: 1,         // Sync the dimming with scrolling
    onUpdate: (self) => {
      const progress = self.progress;

      // Dim the previous card (if available)
      if (index > 0) { // Check if there is a previous card
        const previousCardInner = cards[index - 1].querySelector('.card__inner');

        gsap.to(previousCardInner, {
          filter: `brightness(${gsap.utils.interpolate(1, 0.5, progress)})`, // Dimming the previous card
          overwrite: false // No overwrite to avoid conflicts
        });
      }
    }
  });

  // Floating effect for the cards
  gsap.to(cardInner, {
    y: '+=20', // Slight floating upwards
    repeat: -1, // Infinite loop
    yoyo: true, // Move back and forth
    ease: 'power1.inOut', // Smooth transitions
    duration: gsap.utils.random(1, 2), // Random duration between 1 and 2 seconds
    overwrite: false // No overwrite to avoid conflicts
  });
});
</script>

CSS: 

Put this into your Elementor Custom CSS or in your main container on the page

.card {
    position: sticky !important;
    top: 15vh;
    height: 70vh;
}

@media (max-width: 768px) {
.card {
    position: sticky;
    top: 15vh;
    height: 70vh;
}}

@media (max-width: 480px) {
.card {
    position: sticky;
    top: 15vh;
    height: 70vh;
}}
GDPR Cookie Consent with Real Cookie Banner