Claude Code for GSAP: Professional Web Animations — Claude Skills 360 Blog
Blog / Frontend / Claude Code for GSAP: Professional Web Animations
Frontend

Claude Code for GSAP: Professional Web Animations

Published: April 9, 2027
Read time: 7 min read
By: Claude Skills 360

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.

Keep Reading

Frontend

Claude Code for Chart.js Advanced: Custom Plugins and Mixed Charts

Advanced Chart.js patterns with Claude Code — chart.register() for tree-shaking, mixed chart types combining bar and line, custom plugin API with beforeDraw and afterDatasetsDraw hooks, ScriptableContext for computed colors, ChartDataLabels plugin for value labels, chartjs-plugin-zoom for pan and zoom, custom gradient fills via ctx.createLinearGradient, ChartJS annotation plugin for threshold lines, streaming data with chartjs-plugin-streaming, and react-chartjs-2 with useRef and chart instance.

6 min read Jun 27, 2027
Frontend

Claude Code for Nivo: Rich SVG and Canvas Charts

Build rich data visualizations with Nivo and Claude Code — ResponsiveLine and ResponsiveBar for adaptive charts, ResponsiveHeatMap for matrix data, ResponsiveTreeMap for hierarchal data, ResponsiveSunburst for nested proportions, ResponsiveChord for relationship diagrams, ResponsiveCalendar for activity heat maps, ResponsiveNetwork for force graphs, NivoTheme for consistent styling, tooltip customization with sliceTooltip, and motion config for spring animations.

6 min read Jun 26, 2027
Frontend

Claude Code for Victory Charts: React Native and Web Charts

Build cross-platform charts with Victory and Claude Code — VictoryChart, VictoryLine, VictoryBar, and VictoryScatter for web and React Native, VictoryPie for donut charts, VictoryArea for stacked areas, VictoryAxis for custom axes, VictoryTooltip and VictoryVoronoiContainer for hover tooltips, VictoryBrushContainer for range selection, VictoryZoomContainer for pan and zoom, VictoryLegend for series labels, custom theme with VictoryTheme, and VictoryStack for grouped bars.

6 min read Jun 25, 2027

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free