Scroll Animation: Trigger Word-by-Word Fade 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="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<!-- Include ScrollTrigger plugin -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js"></script>
<!-- Include Split-Type library -->
<script src="https://unpkg.com/split-type"></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>