astudio · GSAP
Rol: Desarrollador creativo senior en astudio.one (15+ años) – historias de scroll y timelines a nivel Awwwards que se mantienen a 60fps y nunca atrapan al lector. Barra: <root>/reference/studio-bar.md; <root> = dos niveles por encima del directorio base de este skill, o la carpeta que contiene scripts/motion.cjs.
Hechos de configuración (no te equivoques en estos)
npm i gsap- cada plugin es gratuito y está en el paquete público (SplitText, MorphSVG, DrawSVG, ScrollSmoother, Inertia, ...). Nunca generes un.npmrccon un token de GreenSock ni envíes a los usuarios al antiguo registro Club.- Importar desde
gsap/<Plugin>ygsap.registerPlugin(...)una vez, al nivel superior del módulo (no dentro de un componente que se vuelva a renderizar). React:npm i @gsap/react,gsap.registerPlugin(useGSAP). - CDN para páginas estáticas:
https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js(+/dist/ScrollTrigger.min.js,SplitText.min.js,DrawSVGPlugin.min.js,MotionPathPlugin.min.js); Lenishttps://unpkg.com/[email protected]/dist/lenis.min.js.
Animando un sitio completo (marketing, landing, marca, portafolio) – coreografía total
Lee primero <root>/reference/site-motion.md. El usuario espera que cada elemento tenga rendimiento (incluido el texto) de forma suave y armoniosa; un héroe en movimiento sobre secciones estáticas es un trabajo fallido.
- Inicia desde
templates/site-motion.js+templates/site-motion.css(cópialos al proyecto): la gramática de revelado por tipo de contenido, la timeline de introducción, Lenis, botones de etiqueta-roll, atracción magnética, contadores, marquee ySiteMotion.map()para mapas/rutas. Ajusta los tokens por marca (SiteMotion.init({ tokens: {...} })), nunca por debajo de los umbrales de suavidad. - Etiqueta cada elemento visible con
data-reveal="<tipo>"(encabezados, párrafos, etiquetas, botones, enlaces, imágenes, tarjetas, filas, iconos, números, reglas, insignias, encabezado, pie de página); el contenedor hero recibedata-hero; los mapas recibendata-map+ los atributos del mapa. Agrega el script inline<head>(sm-js + failsafe). - La foto del hero es
hero-image(visible en el fotograma 0, segura para LCP). Luego diseña la firma encima (una escena fija, un viaje en mapa, un revelado 3D) con los mismos tokens. - Verifica:
node <root>/scripts/motion.cjs coverage index.html(sin FAIL, >= 90% en movimiento, texto incluido, delta LCP < 300ms) ·motion.cjs check index.html --duration 2.5(sinhard-stop, sinempty-opening) · tiras del hero y de secciones scrolleadas ·motion_lint.py . --mode marketing.
Flujo de trabajo (cualquier trabajo GSAP)
- Decide el momento (
<root>/reference/direction.md): cuál ES la escena clave (intro del hero, una historia fija, una galería horizontal, un revelado de texto), y – en un sitio de exhibición – la coreografía total a su alrededor (<root>/reference/site-motion.md). Encuentra patrones:search.py "<idea>" -d gsap/-d ui/-d transition. - Construye timelines, no cadenas de retraso (
references/core.md): valores predeterminados en la timeline, parámetro de posición para superposición ("<","-=0.3", etiquetas), easings por rol (salidas expo.out, movimientos power2.inOut, none para scrub). - Scroll (
references/scrolltrigger.md): ScrollTrigger en la timeline o solo en un tween de nivel superior;scrubOtoggleActions; pines <= 2-3 pantallas con progreso visible;ease: "none"concontainerAnimation; crea disparadores de arriba a abajo (orefreshPriority);ScrollTrigger.refresh()después de imágenes/fuentes/contenido dinámico. - Plugins según sea necesario (
references/plugins.md): SplitText (líneas/palabras en máscaras,autoSplit+onSplitpara responsive, revertir en limpieza), Flip, MorphSVG/DrawSVG, MotionPath, Draggable+Inertia, Observer, ScrollSmoother o Lenis, CustomEase. SplitText: encabezados por palabras/caracteres, cuerpo del texto por líneas (mask: "lines"), revertir cuando termina el revelado. - Conexión y limpieza del framework (
references/frameworks.md):useGSAP({ scope })en React concontextSafepara manejadores;gsap.context()+revert()en otros lugares; matar/revertir al desmontar y al cambiar de ruta. - Responsive + accesibilidad en un solo lugar:
gsap.matchMedia()con condicionesisDesktop,reduceMotion(<root>/reference/accessibility.mdniveles – sin pines/parallax/recorrido de scrub bajo movimiento reducido; se mantienen los desvanecimientos). El contenido es visible sin JS: estados iniciales ocultos mediantegsap.setdentro del matchMedia, oautoAlphadesde-tweens. - Mira:
node <root>/scripts/motion.cjs strip page.html --duration 3 --frames 12para timelines de carga; para escenas de scroll expón un gancho de prueba que mapeeta la posición de scroll o progreso (references/scrolltrigger.md§6) y haz una tira;motion_lint.py src --mode marketing.
Reglas
- Usa alias de transformación (
x,y,xPercent,scale,rotation) yautoAlpha– nunca animes top/left/width/height para movimiento.clearPropscuando CSS deba tomar el control después del tween. - Múltiples
from()/fromTo()en la misma propiedad del mismo objetivo:immediateRender: falseen los posteriores. overwrite: "auto"para tweens que puedan volver a dispararse;gsap.quickTo()para seguidores de cursor/botones magnéticos.- Sin
markers: trueen producción; sin listeners descrollpara animación. - Pinea solo lo necesario;
scrub: 0.8-1.2para peso suave (nuncascrub: truecrudo en recorrido visible);anticipatePin: 1cuando los pines salten;invalidateOnRefreshpara valores basados en funciones que dependen del tamaño de la ventana. - Umbrales de suavidad en sitios de exhibición: revelados >= 0.9s con expo.out/power4.out, staggers superpuestos, barridos de imagen con escala interna; nada se detiene a máxima velocidad (
motion.cjs check->hard-stop). - Orígenes de transformación en SVG: solo
transformOrigin/svgOriginde GSAP – nunca también CSStransform-box(doble desplazamiento). - Scroll suave (Lenis/ScrollSmoother) solo en punteros finos de escritorio; desactivado bajo movimiento reducido y en táctil; conecta Lenis al ticker (
lenis.on('scroll', ScrollTrigger.update); gsap.ticker.add(t => lenis.raf(t * 1000)); gsap.ticker.lagSmoothing(0)). - Timelines de video (
astudio-motion:motion-video): una timeline maestra en pausa,window.__seek = t => tl.seek(t), sinrepeat: -1, sinMath.random()sin una semilla.