Community forum for knowledge and support

Updated 2 weeks ago

I fixed the Layout 484 animation to trigger on scroll into view.

I was working with Layout 484 (the big text animation). It triggers on page load, so unless it’s at the top of your page, you won’t even notice the nice animation. I wanted it to trigger (and re-trigger) when scrolled into view.

I went to Claude, and after just 50 exchanges, I settled on this updated code (to replace in the code block that comes with the component).

Refer to the top of the code for a list of features. Note in the attached gif that the debug markers are on, and I’m showing how it animates from zero, scrolling away mid-animation, and resetting when it’s out of view. Your mileage may vary. Good luck!

Features:

  • Words fade in one-by-one when scrolled into view
  • All words instantly fade out (reset) together when scrolled out of view
  • Prevents animation overlap during rapid scrolling
  • Configurable timing, opacity levels, and trigger points
  • Includes helpful debug markers (REMEMBER TO DISABLE FOR PRODUCTION!)

<!--
GSAP Word-by-Word Scroll Animation
Features:
- Words fade in one-by-one when scrolled into view
- All words instantly fade out (reset) together when scrolled out of view
- Prevents animation overlap during rapid scrolling
- Configurable timing, opacity levels, and trigger points
- Includes helpful debug markers (REMEMBER TO DISABLE FOR PRODUCTION!)

Created by:
- Claude-3.5-Sonnet (Anthropic)
- Lee Fuhr (hi@leefuhr.com)
(I should say: I'm no developer. I provide no support. For support, go where I went: Claude.ai.)

Version 1.0 - January 2024
-->

<!-- Include GSAP library first -->
<script src="<a target="_blank" rel="noopener noreferrer" href="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js">https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js</a>"></script>
<!-- Include ScrollTrigger plugin -->
<script src="<a target="_blank" rel="noopener noreferrer" href="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js">https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js</a>"></script>
<!-- Include Split-Type library -->
<script src="<a target="_blank" rel="noopener noreferrer" href="https://unpkg.com/split-type">https://unpkg.com/split-type</a>"></script>

<script>
window.addEventListener('load', function() {
  // CONFIGURATION SETTINGS
  const config = {
    // CLASSES FOR TARGETING ELEMENTS
    triggerClass: ".section_layout484", // The section that triggers the animation
    textClass: ".layout484_text",  // The text that will be split and animated

    // SCROLL TRIGGER POINTS
    startTrigger: "top 80%",  // Animation starts when top of section reaches 80% up from bottom of viewport
    endTrigger: "bottom 20%", // Animation ends when bottom of section reaches 20% up from bottom of viewport
    showMarkers: true,        // Visual indicators for trigger points (SET TO 'FALSE' FOR PRODUCTION!)

    // ANIMATION PARAMETERS
    initialOpacity: 0.3,      // Starting opacity of words (0 = invisible, 1 = fully visible)
    finalOpacity: 1,          // Ending opacity of words
    animationDuration: 0.8,   // How long the fade takes (in seconds)
    staggerTime: 0.1,         // Delay between each word's animation (in seconds)
    easeType: "power1.out"    // Animation easing function (controls acceleration/deceleration)
  };

  // REGISTER SCROLLTRIGGER PLUGIN
  if (typeof ScrollTrigger !== "undefined") {
    gsap.registerPlugin(ScrollTrigger);
  }

  // INITIALIZE TEXT SPLITTING
  const layoutText = new SplitType(config.textClass, { types: "words" });

  // SET INITIAL STATE
  gsap.set(layoutText.words, { opacity: config.initialOpacity });

  // ANIMATION STATE TRACKING
  let isAnimating = false;
  let currentTimeline = null;

  // ANIMATION FUNCTION
  function animate(toFinal) {
    // IF ANIMATION IS RUNNING, LET IT FINISH
    if (isAnimating) return;

    isAnimating = true;
    
    // CREATE NEW TIMELINE FOR THIS ANIMATION SEQUENCE
    currentTimeline = gsap.timeline({
      onComplete: () => {
        isAnimating = false;
        currentTimeline = null;
      }
    });

    if (toFinal) {
      // STAGGERED ANIMATION FOR APPEARING
      currentTimeline.to(layoutText.words, {
        opacity: config.finalOpacity,
        duration: config.animationDuration,
        stagger: config.staggerTime,
        ease: config.easeType
      });
    } else {
      // INSTANT RESET FOR ALL WORDS
      currentTimeline.to(layoutText.words, {
        opacity: config.initialOpacity,
        duration: config.animationDuration / 2, // Faster reset
        stagger: 0, // No stagger - all words together
        ease: "power1.in"
      });
    }
  }

  // SCROLL TRIGGER CREATION
  const st = ScrollTrigger.create({
    trigger: config.triggerClass,
    start: config.startTrigger,
    end: config.endTrigger,
    markers: config.showMarkers,
    onEnter: () => {
      if (!isAnimating) animate(true);
    },
    onLeave: () => {
      if (!isAnimating) animate(false);
    },
    onEnterBack: () => {
      if (!isAnimating) animate(true);
    },
    onLeaveBack: () => {
      if (!isAnimating) animate(false);
    }
  });
});
</script>

Attachments
image.png
2025-01-06 19.49.02.gif
Add a reply
Sign up and join the conversation on Slack