| name | gsap-react |
| description | Use when using GSAP with React, useGSAP hook, React component animations, ScrollTrigger in React, or managing GSAP lifecycle in React components. |
GSAP React Integration
GSAP integrates seamlessly with React through the @gsap/react package and useGSAP() hook. Automatically handles cleanup, scoping, and lifecycle management.
Installation
npm install gsap @gsap/react
Basic Setup
Register Hook
import gsap from 'gsap'
import { useGSAP } from '@gsap/react'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
gsap.registerPlugin(ScrollTrigger, useGSAP)
Basic useGSAP Hook
import { useGSAP } from '@gsap/react'
import { useRef } from 'react'
function MyComponent() {
const container = useRef()
useGSAP(() => {
gsap.to('.box', { x: 200, duration: 1 })
}, { scope: container })
return <div ref={container}>
<div className="box">Animate me</div>
</div>
}
Selector Scoping
Scope to Container
import { useGSAP } from '@gsap/react'
import { useRef } from 'react'
function MyComponent() {
const container = useRef()
useGSAP(() => {
gsap.to('.box', { x: 200, duration: 1 })
}, { scope: container })
return (
<div ref={container}>
<div className="box">Box 1</div>
<div className="box">Box 2</div>
</div>
)
}
Without Scoping (Global)
useGSAP(() => {
gsap.to('.box', { x: 200, duration: 1 })
})
Dependency Array
Rerun on Dependencies
import { useState } from 'react'
import { useGSAP } from '@gsap/react'
function Counter() {
const [count, setCount] = useState(0)
useGSAP(() => {
gsap.to('.counter', {
scale: 1 + count * 0.1,
duration: 0.5
})
}, { scope: container, dependencies: [count] })
return <div className="counter">{count}</div>
}
Automatic Cleanup
useGSAP(() => {
const tl = gsap.timeline()
tl.to('.box', { x: 200, duration: 1 })
})
ScrollTrigger Integration
Basic ScrollTrigger
import { ScrollTrigger } from 'gsap/ScrollTrigger'
function ScrollComponent() {
const container = useRef()
useGSAP(() => {
gsap.to('.box', {
x: 500,
scrollTrigger: {
trigger: '.box',
start: 'top center',
end: 'bottom center',
scrub: true
}
})
}, { scope: container })
return <div ref={container}>
<div className="box">Scroll me</div>
</div>
}
Multiple ScrollTriggers
useGSAP(() => {
gsap.to('.box1', {
x: 200,
scrollTrigger: {
trigger: '.box1',
start: 'top 80%'
}
})
gsap.to('.box2', {
x: 200,
scrollTrigger: {
trigger: '.box2',
start: 'top 80%'
}
})
}, { scope: container })
Pinning with useGSAP
useGSAP(() => {
const tl = gsap.timeline({
scrollTrigger: {
trigger: '.section',
start: 'top top',
end: '+=500',
pin: true,
scrub: true
}
})
tl.to('.box', { x: 200, duration: 1 })
.to('.box', { y: 200, duration: 1 })
}, { scope: container })
Context Safety
contextSafe for Events
import { useGSAP } from '@gsap/react'
import { useRef } from 'react'
function Button() {
const container = useRef()
useGSAP(() => {
const { contextSafe } = useGSAP(() => {}, { scope: container })
const handleClick = contextSafe(() => {
gsap.to('.box', {
scale: 1.5,
duration: 0.3,
ease: 'back.out(1.7)'
})
})
document.querySelector('.button').addEventListener('click', handleClick)
return () => {
document.querySelector('.button').removeEventListener('click', handleClick)
}
})
return <div ref={container}>
<div className="button">Click me</div>
< =>Box
}
Responsive with matchMedia
Basic matchMedia
useGSAP(() => {
const mm = gsap.matchMedia()
mm.add('(min-width: 768px)', () => {
gsap.to('.box', { x: 200, duration: 1 })
})
mm.add('(max-width: 767px)', () => {
gsap.to('.box', { x: 100, duration: 1 })
})
return () => mm.revert()
}, { scope: container })
Nested matchMedia
useGSAP(() => {
const mm = gsap.matchMedia()
mm.add('(min-width: 1024px)', () => {
gsap.to('.box', { x: 300, duration: 1 })
})
mm.add('(min-width: 768px) and (max-width: 1023px)', () => {
gsap.to('.box', { x: 200, duration: 1 })
})
mm.add('(max-width: 767px)', () => {
gsap.to('.box', { x: 100, duration: 1 })
})
return () => mm.revert()
}, { scope: container })
Dynamic Content
Animate on Data Change
function DataList({ items }) {
const container = useRef()
useGSAP(() => {
gsap.from('.item', {
opacity: 0,
y: 20,
stagger: 0.1,
duration: 0.5
})
}, { scope: container, dependencies: [items] })
return (
<div ref={container}>
{items.map((item, i) => (
<div key={i} className="item">{item}</div>
))}
</div>
)
}
Animate on Mount
function Welcome() {
const container = useRef()
useGSAP(() => {
gsap.from('.welcome', {
opacity: 0,
y: -30,
duration: 0.8,
ease: 'back.out(1.7)'
})
}, { scope: container })
return <div ref={container}>
<div className="welcome">Welcome!</div>
</div>
}
Animate on Unmount
function Modal({ isOpen, onClose }) {
const container = useRef()
useGSAP(() => {
if (isOpen) {
gsap.from('.modal-content', {
scale: 0.8,
opacity: 0,
duration: 0.3,
ease: 'back.out(1.7)'
})
}
return () => {
if (isOpen) {
gsap.to('.modal-content', {
scale: 0.8,
opacity: 0,
duration: 0.2,
ease: 'power2.in'
})
}
}
}, { scope: container, dependencies: [isOpen] })
return isOpen ? (
<div className="modal" ref={container}>
<div className="modal-content">Modal content</div>
</div>
) : null
}
Advanced Patterns
Controlled Timeline
function ControlledAnimation({ isPlaying }) {
const container = useRef()
useGSAP(() => {
const tl = gsap.timeline({ paused: true })
tl.to('.box', { x: 200, duration: 1 })
.to('.box', { y: 200, duration: 1 })
.to('.box', { rotation: 360, duration: 1 })
window.myTimeline = tl
}, { scope: container })
useEffect(() => {
if (isPlaying && window.myTimeline) {
window.myTimeline.play()
} else if (!isPlaying && window.myTimeline) {
window.myTimeline.pause()
}
}, [isPlaying])
return <div =>
Box
}
Flip Animation
import { Flip } from 'gsap/Flip'
gsap.registerPlugin(Flip)
function DraggableList({ items }) {
const container = useRef()
useGSAP(() => {
const state = Flip.getState('.item')
Flip.from(state, {
duration: 0.8,
ease: 'power2.out'
})
}, { scope: container, dependencies: [items] })
return (
<div ref={container}>
{items.map((item, i) => (
<div key={item.id} className="item">
{item.text}
</div>
))}
</div>
)
}
SplitText in React
import { SplitText } from 'gsap/SplitText'
gsap.registerPlugin(SplitText)
function AnimatedText({ text }) {
const container = useRef()
useGSAP(() => {
const split = new SplitText('.text', { type: 'chars' })
gsap.from(split.chars, {
opacity: 0,
y: 20,
stagger: 0.02,
duration: 0.5
})
return () => split.revert()
}, { scope: container, dependencies: [text] })
return (
<div ref={container}>
<div className="text">{text}</div>
</div>
)
}
ScrollTrigger + Dynamic Data
function InfiniteScroll({ data }) {
const container = useRef()
useGSAP(() => {
ScrollTrigger.refresh()
}, { scope: container, dependencies: [data] })
useGSAP(() => {
gsap.from('.item', {
opacity: 0,
y: 50,
stagger: 0.1,
duration: 0.6,
scrollTrigger: {
trigger: '.item',
start: 'top 80%',
toggleActions: 'play none none reverse'
}
})
}, { scope: container, dependencies: [data] })
return (
<div ref={container}>
{data.map((item, i) => (
<div key={i} className="item">{item}</div>
))}
</div>
)
}
Multiple Hooks
Separate Scopes
function MultiAnimation() {
const scope1 = useRef()
const scope2 = useRef()
useGSAP(() => {
gsap.to('.box1', { x: 200, duration: 1 })
}, { scope: scope1 })
useGSAP(() => {
gsap.to('.box2', { y: 200, duration: 1 })
}, { scope: scope2 })
return (
<>
<div ref={scope1}>
<div className="box1">Box 1</div>
</div>
<div ref={scope2}>
<div className="box2">Box 2</div>
</div>
</>
)
}
Strict Mode Handling
useGSAP Handles Strict Mode
function StrictComponent() {
const container = useRef()
useGSAP(() => {
gsap.from('.box', {
opacity: 0,
y: 20,
duration: 0.5
})
}, { scope: container })
return <div ref={container}>
<div className="box">Box</div>
</div>
}
Performance Tips
Memoize Callbacks
import { useCallback } from 'react'
function OptimizedComponent() {
const container = useRef()
const animateBox = useCallback(() => {
gsap.to('.box', { x: 200, duration: 1 })
}, [])
useGSAP(() => {
animateBox()
}, { scope: container, dependencies: [animateBox] })
return <div ref={container}>
<div className="box">Box</div>
</div>
}
Throttle Expensive Updates
useGSAP(() => {
const throttledUpdate = gsap.utils.throttle(() => {
updateLayout()
}, 100)
gsap.to('.box', {
onUpdate: throttledUpdate
})
}, { scope: container })
Common Mistakes
1. Not Scoping Selectors
useGSAP(() => {
gsap.to('.box', { x: 200 })
})
useGSAP(() => {
gsap.to('.box', { x: 200 })
}, { scope: container })
2. Forgetting Dependencies
useGSAP(() => {
gsap.from('.item', { opacity: 0, stagger: 0.1 })
}, { scope: container })
useGSAP(() => {
gsap.from('.item', { opacity: 0, stagger: 0.1 })
}, { scope: container, dependencies: [items] })
3. Manual Cleanup Issues
useEffect(() => {
gsap.to('.box', { x: 200 })
}, [])
useGSAP(() => {
gsap.to('.box', { x: 200 })
}, { scope: container })
Best Practices
- Always use scope - Prevents selector conflicts
- Include dependencies - Rerun animations when data changes
- Use contextSafe for events - Ensures cleanup
- Leverage matchMedia - Responsive animations
- Refresh ScrollTrigger - After dynamic DOM changes
- Use revertOnUpdate - Revert animations on updates
- Clean up timelines - Expose and kill if needed
- Test with Strict Mode - useGSAP handles double-mounts
Quick Reference
| Feature | Method |
|---|
| Import hook | import { useGSAP } from '@gsap/react' |
| Register hook | gsap.registerPlugin(useGSAP) |
| Basic usage | useGSAP(() => { ... }, { scope: ref }) |
| Dependencies | useGSAP(() => { ... }, { dependencies: [data] }) |
| ScrollTrigger | Works automatically with useGSAP |
| matchMedia | gsap.matchMedia().add(...) |
| Context safe | const { contextSafe } = useGSAP(...) |
| Cleanup | Automatic, or return cleanup function |