Skip to main content

gsap-web

GSAP in a React or Next.js app without the leaks -- timelines over stacked tweens, useGSAP and context for cleanup, ScrollTrigger set up so it survives route changes and layout shifts. Use when building GSAP animations in a component-based app.

Datos de origen

Repositorio
OneWave-AI/open-agent-stack
Última actividad en el origen
11 de agosto de 2026 a las 06:23
Idioma detectado de SKILL.md
inglés
Estrellas
5
Forks
0

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
gsap-web
description
GSAP in a React or Next.js app without the leaks -- timelines over stacked tweens, useGSAP and context for cleanup, ScrollTrigger set up so it survives route changes and layout shifts. Use when building GSAP animations in a component-based app.
# GSAP for Web Apps GSAP is straightforward until it meets React. Then every animation that worked in a CodePen starts double-firing in Strict Mode, surviving unmount, and pointing at stale DOM after a route change. The library is not the problem -- lifecycle is. These are the patterns that hold up. ## Setup Install `gsap` and `@gsap/react`. Register plugins once, at module scope in a client component, never inside render: ```js 'use client' import gsap from 'gsap' import { ScrollTrigger } from 'gsap/ScrollTrigger' import { useGSAP } from '@gsap/react' gsap.registerPlugin(ScrollTrigger, useGSAP) gsap.defaults({ duration: 0.28, ease: 'power3.out' }) // from motion-system ``` In Next.js App Router, anything touching GSAP is a client component. GSAP reads the DOM, so it cannot run during SSR -- a `window is not defined` build error means the import climbed into a server component. ## Workflow 1. **Scope everything.** `useGSAP` with a container ref. It reverts every animation created inside on unmount and handles Strict Mode's double-invoke, which is the source of nearly every "it fires twice" report. ```js const container = useRef(null) useGSAP(() => { gsap.from('.card', { y: 24, opacity: 0, stagger: 0.05 }) }, { scope: container }) ``` Selector strings inside the callback resolve within the scope, so `.card` cannot reach into another component's markup. 2. **Timelines, not stacked tweens.** Three sequenced tweens with hand-computed delays become unmaintainable the moment one duration changes. One timeline with the position parameter stays readable: ```js const tl = gsap.timeline() tl.from('.title', { y: 30, opacity: 0 }) .from('.sub', { y: 20, opacity: 0 }, '-=0.15') // overlap .from('.cta', { scale: 0.96, opacity: 0 }, '<') // with previous ``` Use labels (`tl.addLabel('reveal')`) once a timeline passes about five steps. 3. **`from` vs `fromTo`.** `from` animates *to the element's current CSS*, which is fragile when other code or a re-render changes that state -- an interrupted `from` can leave the element stuck at its start values. For anything that can be interrupted or replayed, use `fromTo` and state both ends explicitly. Add `immediateRender: false` when a `from` inside a timeline flashes its end state on load. 4. **ScrollTrigger, carefully.** Attach the trigger to the animation, not the other way around, and let `useGSAP` clean it up: ```js gsap.to('.panel', { y: -80, scrollTrigger: { trigger: '.panel', start: 'top 80%', end: 'bottom 20%', scrub: 1, // number = smoothing lag in seconds // markers: true, // dev only -- never commit this }, }) ``` After images, fonts, or async content land, call `ScrollTrigger.refresh()` -- stale start/end positions are the number-one ScrollTrigger bug. In Next.js, refresh on route change too. 5. **Verify the teardown.** Navigate away and back twice. Animations should not stack, ScrollTriggers should not multiply (`ScrollTrigger.getAll().length` stays flat), and nothing should throw about a null element. If any of those fail, something was created outside `useGSAP`'s scope. ## Rules - Animate `x`/`y`/`scale`/`rotation`/`opacity`. GSAP's `x`/`y` compile to `transform` -- animating `top`/`left` instead costs layout on every frame. - Never animate in a `useEffect` without cleanup. `useGSAP` exists precisely so you do not have to remember `ctx.revert()`. - Set `will-change` through GSAP's own `force3D` behavior or not at all; a permanent `will-change: transform` on many elements is a memory cost, not a speedup. - Kill `markers: true` before committing. It ships to production more often than you would think. - Respect reduced motion at the source with `gsap.matchMedia()`, so the reduced branch is a real alternative timeline rather than a disabled one: ```js const mm = gsap.matchMedia() mm.add('(prefers-reduced-motion: no-preference)', () => { /* full timeline */ }) mm.add('(prefers-reduced-motion: reduce)', () => { /* fades only */ }) ``` - Do not reach for GSAP when CSS suffices. A hover state, a fade-in, a spinner -- those are transitions and keyframes. GSAP earns its bundle on sequenced timelines, scroll scrubbing, and anything needing runtime control. - GSAP's ScrollSmoother, SplitText, and other formerly-paid plugins are free as of GSAP 3.13, but every plugin is bundle weight -- add one only when the effect is core to the page.
Ver en GitHub