GSAP (GreenSock Animation Platform) is the industry standard for high-performance web animations — gsap.to(element, { x: 100, opacity: 1, duration: 0.5 }) animates to target values. gsap.from(element, { y: -50, opacity: 0 }) animates from values. gsap.timeline() sequences animations with .to(), .from(), and .fromTo(). ScrollTrigger ties animations to scroll position with trigger, start, end, and scrub. useGSAP(() => { ... }, { scope: container }) from @gsap/react handles cleanup automatically in React. gsap.stagger animates lists with per-item delay. SplitText splits text into chars/words for character-by-character animation. Draggable.create(element) adds drag-and-drop. GSAP automatically uses GPU-accelerated transforms and avoids layout thrashing. Claude Code generates GSAP timelines, scroll-triggered reveals, stagger animations, and React integration patterns with proper cleanup.
CLAUDE.md for GSAP
## GSAP Stack
- Version: gsap >= 3.12 + @gsap/react >= 2.1
- Tween: gsap.to(ref.current, { x: 100, opacity: 1, duration: 0.5, ease: "power2.out" })
- Timeline: const tl = gsap.timeline(); tl.from(el, {...}).to(el2, {...}, "<0.2")
- React: useGSAP(() => { gsap.to(target, opts) }, { scope: containerRef }) — auto cleanup
- ScrollTrigger: gsap.registerPlugin(ScrollTrigger) — scrollTrigger: { trigger, start, end, scrub }
- Stagger: gsap.from(items, { opacity: 0, y: 20, stagger: 0.1 })
- Context: gsap.context(() => { ... }, scope) — scoped selectors + revert()
- Ease: "power2.out" / "elastic.out(1, 0.3)" / "back.out(1.7)" / CustomEase
- Perf: transform: translate3d(0,0,0) / will-change: transform on animated elements
Basic Animations with useGSAP
// components/animations/HeroSection.tsx — entry animations
"use client"
import { useRef } from "react"
import { useGSAP } from "@gsap/react"
import gsap from "gsap"
// Register plugins once at module level
gsap.registerPlugin() // Add ScrollTrigger, etc. here
export function HeroSection() {
const containerRef = useRef<HTMLDivElement>(null)
const headingRef = useRef<HTMLHeadingElement>(null)
const subRef = useRef<HTMLParagraphElement>(null)
const ctaRef = useRef<HTMLDivElement>(null)
// useGSAP handles cleanup — no need for manual revert()
useGSAP(() => {
const tl = gsap.timeline({ defaults: { ease: "power3.out" } })
tl
.from(headingRef.current, { y: 60, opacity: 0, duration: 0.8 })
.from(subRef.current, { y: 30, opacity: 0, duration: 0.6 }, "-=0.4")
.from(ctaRef.current, { y: 20, opacity: 0, duration: 0.5 }, "-=0.3")
}, { scope: containerRef })
return (
<div ref={containerRef} className="py-24 text-center space-y-6">
<h1 ref={headingRef} className="text-5xl font-bold">
Build faster with Claude Code
</h1>
<p ref={subRef} className="text-xl text-muted-foreground max-w-2xl mx-auto">
AI-powered code generation for every library and framework.
</p>
<div ref={ctaRef} className="flex gap-4 justify-center">
<button className="btn-primary px-8">Get started</button>
<button className="btn-outline px-8">View demos</button>
</div>
</div>
)
}
ScrollTrigger Animations
// components/animations/ScrollReveal.tsx — scroll-driven animations
"use client"
import { useRef } from "react"
import { useGSAP } from "@gsap/react"
import gsap from "gsap"
import { ScrollTrigger } from "gsap/ScrollTrigger"
gsap.registerPlugin(ScrollTrigger)
interface ScrollRevealProps {
children: React.ReactNode
animation?: "fadeUp" | "fadeIn" | "slideLeft" | "scale"
delay?: number
className?: string
}
export function ScrollReveal({
children,
animation = "fadeUp",
delay = 0,
className,
}: ScrollRevealProps) {
const ref = useRef<HTMLDivElement>(null)
useGSAP(() => {
const el = ref.current
if (!el) return
const animations = {
fadeUp: { from: { y: 50, opacity: 0 }, to: {} },
fadeIn: { from: { opacity: 0 }, to: {} },
slideLeft: { from: { x: -60, opacity: 0 }, to: {} },
scale: { from: { scale: 0.8, opacity: 0 }, to: {} },
}
const { from } = animations[animation]
gsap.from(el, {
...from,
duration: 0.7,
delay,
ease: "power2.out",
scrollTrigger: {
trigger: el,
start: "top 85%", // Trigger when top of element hits 85% viewport
end: "bottom 20%",
toggleActions: "play none none reverse",
},
})
}, { scope: ref })
return <div ref={ref} className={className}>{children}</div>
}
// Parallax section with scrub
export function ParallaxSection({ children, speed = 0.5 }: { children: React.ReactNode; speed?: number }) {
const ref = useRef<HTMLDivElement>(null)
const innerRef = useRef<HTMLDivElement>(null)
useGSAP(() => {
gsap.to(innerRef.current, {
y: () => -(ref.current?.offsetHeight ?? 0) * speed,
ease: "none",
scrollTrigger: {
trigger: ref.current,
start: "top bottom",
end: "bottom top",
scrub: true, // Ties animation progress to scroll position
},
})
}, { scope: ref })
return (
<div ref={ref} className="overflow-hidden">
<div ref={innerRef}>{children}</div>
</div>
)
}
// Horizontal scroll section
export function HorizontalScroll({ items }: { items: { id: string; content: React.ReactNode }[] }) {
const containerRef = useRef<HTMLDivElement>(null)
const trackRef = useRef<HTMLDivElement>(null)
useGSAP(() => {
const container = containerRef.current
const track = trackRef.current
if (!container || !track) return
const totalWidth = track.scrollWidth - window.innerWidth
gsap.to(track, {
x: -totalWidth,
ease: "none",
scrollTrigger: {
trigger: container,
start: "top top",
end: () => `+=${totalWidth}`,
pin: true,
scrub: 1,
anticipatePin: 1,
},
})
}, { scope: containerRef })
return (
<div ref={containerRef} className="overflow-hidden">
<div ref={trackRef} className="flex" style={{ width: `${items.length * 100}vw` }}>
{items.map(item => (
<div key={item.id} className="w-screen flex-shrink-0 p-16">
{item.content}
</div>
))}
</div>
</div>
)
}
Stagger List Animations
// components/animations/StaggerList.tsx — staggered list reveals
"use client"
import { useRef } from "react"
import { useGSAP } from "@gsap/react"
import gsap from "gsap"
import { ScrollTrigger } from "gsap/ScrollTrigger"
gsap.registerPlugin(ScrollTrigger)
export function FeatureGrid({ features }: { features: { icon: string; title: string; description: string }[] }) {
const gridRef = useRef<HTMLDivElement>(null)
useGSAP(() => {
// Scope selector — ".card" matches only within gridRef
gsap.from(".card", {
y: 40,
opacity: 0,
duration: 0.5,
stagger: {
amount: 0.6, // Total stagger time spread across all elements
from: "start", // "start" | "end" | "center" | "random" | index
ease: "power1.in",
},
ease: "power2.out",
scrollTrigger: {
trigger: gridRef.current,
start: "top 80%",
},
})
}, { scope: gridRef })
return (
<div ref={gridRef} className="grid grid-cols-3 gap-6">
{features.map(feature => (
<div key={feature.title} className="card p-6 rounded-xl border bg-card">
<div className="text-3xl mb-3">{feature.icon}</div>
<h3 className="font-semibold mb-2">{feature.title}</h3>
<p className="text-sm text-muted-foreground">{feature.description}</p>
</div>
))}
</div>
)
}
Interactive Button Animation
// components/animations/AnimatedButton.tsx — hover/tap micro-interactions
"use client"
import { useRef } from "react"
import { useGSAP } from "@gsap/react"
import gsap from "gsap"
import type { ButtonHTMLAttributes } from "react"
interface AnimatedButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: "primary" | "magnetic"
}
export function AnimatedButton({
children,
variant = "primary",
className,
...props
}: AnimatedButtonProps) {
const ref = useRef<HTMLButtonElement>(null)
useGSAP(() => {
const el = ref.current
if (!el) return
if (variant === "magnetic") {
// Magnetic hover effect
const handleMouseMove = (e: MouseEvent) => {
const rect = el.getBoundingClientRect()
const centerX = rect.left + rect.width / 2
const centerY = rect.top + rect.height / 2
const deltaX = (e.clientX - centerX) * 0.3
const deltaY = (e.clientY - centerY) * 0.3
gsap.to(el, { x: deltaX, y: deltaY, duration: 0.3, ease: "power2.out" })
}
const handleMouseLeave = () => {
gsap.to(el, { x: 0, y: 0, duration: 0.5, ease: "elastic.out(1, 0.3)" })
}
el.addEventListener("mousemove", handleMouseMove)
el.addEventListener("mouseleave", handleMouseLeave)
return () => {
el.removeEventListener("mousemove", handleMouseMove)
el.removeEventListener("mouseleave", handleMouseLeave)
}
}
// Default: scale tap feedback
const handleMouseDown = () => gsap.to(el, { scale: 0.95, duration: 0.1 })
const handleMouseUp = () => gsap.to(el, { scale: 1, duration: 0.3, ease: "back.out(2)" })
el.addEventListener("mousedown", handleMouseDown)
el.addEventListener("mouseup", handleMouseUp)
el.addEventListener("mouseleave", handleMouseUp)
return () => {
el.removeEventListener("mousedown", handleMouseDown)
el.removeEventListener("mouseup", handleMouseUp)
el.removeEventListener("mouseleave", handleMouseUp)
}
}, { scope: ref })
return (
<button ref={ref} className={className ?? "btn-primary"} {...props}>
{children}
</button>
)
}
SVG Path Animation
// components/animations/AnimatedLogo.tsx — SVG drawing animation
"use client"
import { useRef } from "react"
import { useGSAP } from "@gsap/react"
import gsap from "gsap"
import { DrawSVGPlugin } from "gsap/DrawSVGPlugin"
gsap.registerPlugin(DrawSVGPlugin)
export function AnimatedCheckmark({ size = 64 }: { size?: number }) {
const svgRef = useRef<SVGSVGElement>(null)
useGSAP(() => {
const tl = gsap.timeline()
// Draw SVG path from 0% to 100%
tl
.from(".circle", {
drawSVG: "0%",
duration: 0.6,
ease: "power2.in",
})
.from(".check", {
drawSVG: "0%",
duration: 0.4,
ease: "power2.out",
}, "-=0.1")
.from(".circle", {
scale: 0.8,
transformOrigin: "center center",
duration: 0.2,
ease: "back.out(2)",
}, 0)
}, { scope: svgRef })
return (
<svg ref={svgRef} width={size} height={size} viewBox="0 0 64 64">
<circle
className="circle"
cx="32" cy="32" r="28"
fill="none"
stroke="currentColor"
strokeWidth="3"
strokeLinecap="round"
/>
<path
className="check"
d="M18 32 L27 41 L46 22"
fill="none"
stroke="currentColor"
strokeWidth="3"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
For the Framer Motion alternative when a React-native animation library with motion.div, layout animations, AnimatePresence for exit animations, and gesture recognition (drag, hover, tap) are preferred over GSAP’s imperative API — Framer Motion has a declarative JSX-first syntax better suited for UI state animations and component transitions, see the Framer Motion guide. For the CSS animation alternative when browser-native keyframes, @keyframes, animation shorthand, and View Transitions API are sufficient without a JavaScript library — CSS handles simple entrance/exit animations with zero runtime overhead, see the CSS animation guide. The Claude Skills 360 bundle includes GSAP skill sets covering timelines, ScrollTrigger, and React integration. Start with the free tier to try professional animation generation.