GSAP ScrollTrigger Lifecycle & Cleanup in React 19 / Next.js
How to safely orchestrate pinned scroll animations, smooth scrolling (Lenis), and client-side page transitions without memory leaks or layout thrashing.
The Core Problem: Zombie ScrollTriggers
When orchestrating complex pinned animations in single-page applications, route transitions frequently leave dangling ScrollTrigger instances attached to DOM nodes that no longer exist. In React 19, strict concurrency and component lifecycle timing make manual tween tracking prone to race conditions.
The Solution: gsap.context() as a Lifetime Boundary
By wrapping every timeline and trigger inside `gsap.context()`, you establish an isolated execution boundary. Calling `ctx.revert()` in the cleanup callback of your `useEffect` guarantees that all child timelines are stopped, inline styles applied by GSAP are rolled back to their initial state, and ScrollTriggers are unregistered.
useEffect(() => {
const ctx = gsap.context(() => {
// All selectors inside are automatically scoped
gsap.to(".anim-target", {
scrollTrigger: {
trigger: "#section",
start: "top top",
end: "+=100%",
scrub: true,
pin: true,
},
scale: 1.2,
opacity: 0,
});
});
return () => {
// Reverts inline styles and unregisters all ScrollTriggers cleanly
ctx.revert();
};
}, []);Coordinating Lenis and ScrollTrigger
When integrating smooth scrolling via Lenis, never let Lenis and GSAP run separate animation frames. Wire Lenis into GSAP's central ticker so that scroll position updates and timeline calculations execute synchronously in the exact same microtask tick.
const lenis = new Lenis({ lerp: 0.08 });
lenis.on("scroll", ScrollTrigger.update);
const tickerCallback = (time: number) => {
lenis.raf(time * 1000);
};
gsap.ticker.add(tickerCallback);
gsap.ticker.lagSmoothing(0);Always disable custom smooth scrolling on touch devices (`navigator.maxTouchPoints > 0`). Native inertial scrolling on iOS and Android provides superior tactile responsiveness.
Mobile Viewport Resizing (Address Bar Collapse)
Mobile WebKit resizes the inner height as the user scrolls, triggering ScrollTrigger recalculations that cause jarring visual stutter. Call `ScrollTrigger.normalizeScroll(true)` on touch devices to lock scroll containment and eliminate address bar jitter.