From lll-animation
This skill should be used when a participant asks how to use GSAP or Three.js inside a React component, how to target DOM elements with GSAP in React, how to set up a Three.js scene in a useEffect, or why their animation works in vanilla JS but not in their .tsx file.
npx claudepluginhub simon-tanna/lll-animation-plugin --plugin lll-animationThis skill uses the workspace's default tool permissions.
The LLL Animation Workshop uses Vite + React TypeScript. GSAP and Three.js both work with React, but require specific integration patterns to avoid bugs.
Integrates GSAP animations in React/Next.js using useGSAP hook, refs, gsap.context(), dependency arrays, and automatic unmount cleanup.
Provides architecture patterns for integrating Three.js, GSAP ScrollTrigger, React Three Fiber, Framer Motion, and React Spring in complex 3D web experiences. Use for multi-library setups, scroll-driven animations, physics interactions, state management, and performance optimization.
Guides GSAP and ScrollTrigger usage for web animations including tweens, timelines, scroll-triggered effects, pinning, scrubbing, and parallax on DOM, SVG, Canvas, WebGL.
Share bugs, ideas, or general feedback.
The LLL Animation Workshop uses Vite + React TypeScript. GSAP and Three.js both work with React, but require specific integration patterns to avoid bugs.
React manages the DOM — components render JSX which React turns into real DOM elements. This creates two challenges for animation libraries:
document.querySelector('.cube') is fragile in React. Use useRef to get a stable reference to the specific element in your component instance.The fix for all three: use useRef to get a stable reference to the DOM element, and useEffect to run animation code after the DOM is ready.
import { useRef, useEffect } from 'react'
import gsap from 'gsap'
function Cube() {
// Separate refs for the two animated containers
const positionRef = useRef<HTMLDivElement>(null) // position container (translate)
const cubeRef = useRef<HTMLDivElement>(null) // cube container (roll rotation)
useEffect(() => {
const position = positionRef.current
const cube = cubeRef.current
if (!position || !cube) return
function roll() {
const tl = gsap.timeline({
onComplete: () => {
// Reset after each roll — the core technique
gsap.set(cube, { rotationZ: 0 })
roll()
}
})
tl.to(position, { x: 100, y: 100, duration: 0.5 })
tl.to(cube, { rotationZ: -90, duration: 0.5 }, '<')
}
roll()
// Kill tweens when component unmounts (e.g. hot module replacement)
return () => gsap.killTweensOf([position, cube])
}, []) // Empty deps = run once after mount
return (
<div ref={positionRef} className="position-container">
<div className="perspective-container">
<div ref={cubeRef} className="cube">
{/* 6 face divs */}
</div>
</div>
</div>
)
}
ref={positionRef} to the position container (outermost div) — GSAP animates its x/y for movement.ref={cubeRef} to the cube container (innermost) — GSAP animates its rotation for rolling.useEffect with [] runs once after mount. This is the correct place for animation setup.useEffect to kill tweens on unmount.import { useRef, useEffect } from 'react'
import * as THREE from 'three'
import gsap from 'gsap'
function Cube3D() {
const canvasRef = useRef<HTMLCanvasElement>(null)
useEffect(() => {
const canvas = canvasRef.current
if (!canvas) return
// The boilerplate already provides scene, camera, renderer, and render loop.
// Add your mesh to the existing scene reference from the boilerplate.
const geometry = new THREE.BoxGeometry(1, 1, 1)
const material = new THREE.MeshBasicMaterial({ color: 0x4fd1c5 })
const cube = new THREE.Mesh(geometry, material)
// scene.add(cube) — use the boilerplate's scene
// Cleanup on unmount — prevents GPU memory leaks during HMR
return () => {
geometry.dispose()
material.dispose()
gsap.killTweensOf(cube.rotation)
gsap.killTweensOf(cube.position)
}
}, [])
return <canvas ref={canvasRef} />
}
Workshop note: The starter repo's
Cube3D.tsxboilerplate already initialises the scene, camera, renderer, and render loop. You don't need to create these — just add your mesh to the existing scene and animate it with GSAP.
useRef<HTMLCanvasElement> for the canvas element if you need to pass it to WebGLRenderer — the workshop boilerplate handles this for you, but the pattern is the same.useEffect — the canvas must exist in the DOM first.geometry.dispose() and material.dispose() on unmount to free GPU memory.| HTML attribute | JSX equivalent |
|---|---|
class="cube" | className="cube" |
style="transform-style: preserve-3d" | style={{ transformStyle: 'preserve-3d' }} |
onclick="fn()" | onClick={fn} |
<br> | <br /> (must be self-closing) |
Inline styles in JSX take a JavaScript object with camelCase property names. For complex animation, prefer external CSS classes — GSAP targets DOM elements directly and does not interact with React's render cycle.
phase1-css-cube — CSS cube construction and isometric projectionisometric-rolling-cube — Rolling animation technique and direction logicgsap-expert — Full GSAP API referencethreejs-fundamentals — Three.js scene setup and Object3D hierarchy