Skip to content
← All notes

Animation//4 min read

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.

#GSAP#React 19#Next.js#ScrollTrigger#Performance

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.

typescript
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.

typescript
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);

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.