Patterns

Patterns — GSAP

Timeline sequencing with labels

When to use: any multi-step animation where steps need to be reordered, reused, or referenced from outside the timeline. How: tl.addLabel("intro") then position later tweens relative to it (tl.to(el, {...}, "intro+=0.3")). Use "<" (start of previous tween) and ">" (end of previous tween) for relative positioning without hardcoding times. Trade-offs: labels make timelines self-documenting and resilient to duration changes upstream; raw time offsets ("+=1" from timeline start) are fragile once earlier tweens change duration.

Cleanup with gsap.context()

When to use: any component-based framework (React, Vue, Svelte) where animations must be torn down on unmount. How: wrap setup in let ctx = gsap.context(() => { gsap.to(".box", {...}) }, scopeRef); return () => ctx.revert();. All tweens/ScrollTriggers/selectors created inside are scoped and reverted together. Trade-offs: without this, tweens and ScrollTriggers leak across remounts (stacking listeners, stale references to unmounted DOM nodes) — this is the single most common GSAP+framework bug.

Responsive animation with gsap.matchMedia()

When to use: animation that must differ (or be disabled) across breakpoints, e.g. simpler/no ScrollTrigger pin on mobile. How: let mm = gsap.matchMedia(); mm.add("(min-width: 800px)", () => { ... return () => cleanup(); }); — GSAP handles add/remove automatically as the breakpoint crosses. Trade-offs: cleaner than manual matchMedia + resize listeners; each block's own cleanup function runs automatically when that block no longer matches.

Reusable per-property tweens with gsap.quickTo()

When to use: high-frequency updates driven by mousemove, drag, or scroll — e.g. a cursor-follow effect. How: let xTo = gsap.quickTo(el, "x", {duration: 0.4, ease: "power3"}); window.addEventListener("mousemove", e => xTo(e.clientX)); — each call retargets the same tween instead of creating a new one. Trade-offs: dramatically cheaper than calling gsap.to() on every event (no repeated tween creation/GC pressure); loses the flexibility of a full tween config per call.

ScrollTrigger scrub for scroll-linked animation

When to use: animation that should track scroll position directly (parallax, progress bars) rather than play once when scrolled into view. How: gsap.to(el, {x: 300, scrollTrigger: {trigger: el, start: "top center", end: "bottom center", scrub: true}}});scrub: true locks 1:1 to scroll; a number adds a smoothing lag in seconds. Trade-offs: scrub disables the tween's own easing curve over time (position is driven by scroll, not the clock) — pick scrub for progress-tied motion, leave it off (with toggleActions) for a one-shot reveal.

Flip for layout-change animation

When to use: animating an element between two different DOM states/positions (reparenting, grid-to-list, expand/collapse) without manually computing the delta. How: let state = Flip.getState(".card"); container.appendChild(card); Flip.from(state, {duration: 0.6, ease: "power1.inOut"}); — Flip measures before, applies the real DOM change, then animates from old to new. Trade-offs: much simpler than hand-computed transform deltas for layout changes; requires the "before" state to be captured synchronously before the DOM mutation.

Stagger for multi-element choreography

When to use: animating a list/grid of elements with a cascading rather than simultaneous start. How: gsap.to(".item", {y: 0, stagger: {each: 0.05, from: "center", grid: "auto"}}});from controls the origin point of the cascade, grid enables 2D-aware staggering. Trade-offs: far more expressive than manually looping and offsetting delays; the object form ({each, from, grid}) trades a little verbosity for a lot of control over the wave pattern.

Performance: killTweensOf and will-change discipline

When to use: interactive/repeated animations (drag, hover) where stale tweens can pile up. How: gsap.killTweensOf(el) before starting a new tween on the same target if overlapping tweens shouldn't coexist; let GSAP manage force3D/GPU promotion rather than hand-setting will-change broadly (it can hurt memory if left on too many elements). Trade-offs: killing tweens defensively avoids conflicting property writes, but killing too aggressively can cut off animations the user expects to keep running (e.g. a fade-out already in flight).