# Animation Patterns in React
---
## 1. Executive Summary & Strategic Necessity
### 1.1 Context (ภาษาไทย)
การสร้าง Animation ที่มีประสิทธิภาพและสวยงามเป็นสิ่งสำคัญในการสร้างประสบการณ์ผู้ใช้ (User Experience) ที่น่าประทับใจและแตกต่าง ในยุค Digital Transformation ที่ผู้ใช้คาดหวังการโต้ตอบที่ลื่นไหลและตอบสนองทันที Animation ไม่เพียงแต่เป็นการตกแต่ง แต่เป็นส่วนสำคัญในการสื่อสารสถานะของระบบ แนะนำการกระทำ และสร้างความสัมพันธ์ทางอารมณ์กับผู้ใช้
Skill นี้ครอบคลุมการใช้งาน Animation Libraries หลักใน React Ecosystem ได้แก่ CSS Animations, Framer Motion, GSAP, และ React Spring พร้อมตัวอย่างโค้ดและ Best Practices สำหรับการสร้าง Animation ที่มีประสิทธิภาพ คำนึงถึง Performance และ Accessibility
### 1.2 Business Impact (ภาษาไทย)
**ผลกระทบทางธุรกิจ:**
1. **เพิ่ม Conversion Rate** - Animation ที่ดีช่วยนำผู้ใช้ไปสู่การกระทำที่ต้องการ (Call-to-Action) ได้ดีขึ้น การศึกษาพบว่า Animation ที่ดีสามารถเพิ่ม Conversion Rate ได้ถึง 15-20%
2. **ลด Bounce Rate** - Loading Animations และ Micro-interactions ที่ดีช่วยลดความรู้สึกว่าระบบช้า ทำให้ผู้ใช้อยู่ในเว็บไซต์นานขึ้น
3. **เพิ่ม Brand Differentiation** - Animation ที่เป็นเอกลักษณ์ช่วยสร้างความแตกต่างจากคู่แข่งและสร้าง Brand Recall ที่แข็งแกร่ง
4. **ปรับปรุง User Retention** - Animation ที่ดีสร้างความพึงพอใจในการใช้งาน ทำให้ผู้ใช้กลับมาใช้งานซ้ำ
5. **ลด Support Cost** - Animation ที่ช่วยแนะนำการใช้งาน (Onboarding Animations) สามารถลดคำถามและปัญหาการใช้งาน
### 1.3 Product Thinking (ภาษาไทย)
**มุมมองด้านผลิตภัณฑ์:**
1. **Purpose-Driven Animation** - ทุก Animation ต้องมีวัตถุประสงค์ที่ชัดเจน ไม่ใช่แค่การตกแต่ง แต่ต้องช่วยให้ผู้ใช้เข้าใจสถานะของระบบ หรือนำทางการกระทำ
2. **Performance-First** - Animation ต้องไม่ส่งผลกระทบต่อ Performance ของแอปพลิเคชัน ต้องใช้ GPU-accelerated properties (transform, opacity)
3. **Accessibility** - Animation ต้องเคารพค่ากำหนดของผู้ใช้ `prefers-reduced-motion` และมี fallback สำหรับผู้ที่มีปัญหาด้านการมองเห็น
4. **Consistent Design Language** - Animation ต้องสอดคล้องกับ Design System และ Brand Guidelines ขององค์กร
5. **Measurable Impact** - Animation ต้องมีการวัดผล (A/B Testing) เพื่อยืนยันว่ามีประโยชน์ต่อผลิตภัณฑ์จริง
---
## 2. Technical Deep Dive (The "How-to")
### 2.1 Core Logic
Animation ใน React สามารถแบ่งออกเป็น 4 ประเภทหลัก:
1. **CSS Animations** - ใช้ CSS Transitions และ Keyframes เหมาะสำหรับ Animation ที่เรียบง่ายและไม่ต้องการ JavaScript control
2. **Framer Motion** - React Animation Library ที่มี API ที่ใช้งานง่ายและทรงพลัง เหมาะสำหรับ React Applications
3. **GSAP (GreenSock)** - Animation Library ที่ทรงพลังและยืดหยุ่นสูง เหมาะสำหรับ Animation ที่ซับซ้อน
4. **React Spring** - Physics-based Animation Library ที่เน้น Spring Physics เหมาะสำหรับ Animation ที่ต้องการความสมจริง
### 2.2 Architecture Diagram Requirements
```
┌─────────────────────────────────────────────────────────────────┐
│ Animation Architecture │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ React │ │ React │ │ React │ │
│ │ Components │◄──►│ Components │◄──►│ Components │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ │ │ │ │
│ ┌──────▼───────────────────▼───────────────────▼───────┐ │
│ │ Animation Layer │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ CSS │ │ Framer │ │ GSAP │ │ │
│ │ │ Animations │ │ Motion │ │ │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ React Spring │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────┘ │
│ │ │
│ │ │
│ ┌────────────────────────▼─────────────────────────────────┐ │
│ │ Performance & Accessibility Layer │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ GPU │ │ Reduced │ │ Focus │ │ │
│ │ │ Accelerated │ │ Motion │ │ Management │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### 2.3 Implementation Workflow
**Step 1: Choose the Right Animation Library**
```typescript
// Decision Tree for Animation Library Selection
function chooseAnimationLibrary(requirements: {
complexity: 'simple' | 'medium' | 'complex'
performance: 'critical' | 'standard'
control: 'css-only' | 'javascript' | 'advanced'
physics: boolean
}) {
if (requirements.complexity === 'simple' && requirements.control === 'css-only') {
return 'CSS Animations'
}
if (requirements.complexity === 'medium' && requirements.control === 'javascript') {
return 'Framer Motion'
}
if (requirements.complexity === 'complex' && requirements.control === 'advanced') {
return 'GSAP'
}
if (requirements.physics) {
return 'React Spring'
}
return 'Framer Motion' // Default choice
}
```
**Step 2: Implement Basic Animation Pattern**
```typescript
// Base Animation Component
"use client"
import { motion } from "framer-motion"
import { useReducedMotion } from "framer-motion"
interface AnimationProps {
children: React.ReactNode
delay?: number
duration?: number
direction?: 'up' | 'down' | 'left' | 'right' | 'none'
}
export function AnimatedSection({
children,
delay = 0,
duration = 0.5,
direction = 'up'
}: AnimationProps) {
const prefersReducedMotion = useReducedMotion()
const getInitialPosition = () => {
if (prefersReducedMotion) return { opacity: 0 }
switch (direction) {
case 'up': return { opacity: 0, y: 50 }
case 'down': return { opacity: 0, y: -50 }
case 'left': return { opacity: 0, x: 50 }
case 'right': return { opacity: 0, x: -50 }
default: return { opacity: 0 }
}
}
return (
{children}
)
}
```
**Step 3: Add Accessibility Support**
```typescript
// Accessibility-aware Animation Hook
function useAccessibleAnimation() {
const [prefersReducedMotion, setPrefersReducedMotion] = useState(false)
useEffect(() => {
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)")
setPrefersReducedMotion(mediaQuery.matches)
const listener = (event: MediaQueryListEvent) => {
setPrefersReducedMotion(event.matches)
}
mediaQuery.addEventListener("change", listener)
return () => mediaQuery.removeEventListener("change", listener)
}, [])
return {
shouldAnimate: !prefersReducedMotion,
animationDuration: prefersReducedMotion ? 0 : 0.5,
}
}
```
---
## 3. Tooling & Tech Stack
### 3.1 Enterprise Tools
| Tool | Purpose | Version | License |
|------|---------|---------|---------|
| Framer Motion | React Animation Library | ^11.0.0 | MIT |
| GSAP | Professional Animation Platform | ^3.12.0 | Commercial/Standard |
| React Spring | Physics-based Animations | ^9.7.0 | MIT |
| Tailwind CSS | Utility-first CSS Framework | ^3.4.0 | MIT |
| TypeScript | Type Safety | ^5.0.0 | Apache 2.0 |
### 3.2 Configuration Essentials
**Framer Motion Setup:**
```bash
npm install framer-motion
```
**GSAP Setup:**
```bash
npm install gsap
```
**React Spring Setup:**
```bash
npm install @react-spring/web
```
**Tailwind Animation Configuration:**
```javascript
// tailwind.config.js
module.exports = {
theme: {
extend: {
animation: {
'fade-in': 'fadeIn 0.5s ease-in-out',
'slide-up': 'slideUp 0.4s ease-out',
'slide-down': 'slideDown 0.4s ease-out',
'scale-in': 'scaleIn 0.3s ease-out',
'shimmer': 'shimmer 2s infinite',
},
keyframes: {
fadeIn: {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
slideUp: {
'0%': { transform: 'translateY(20px)', opacity: '0' },
'100%': { transform: 'translateY(0)', opacity: '1' },
},
slideDown: {
'0%': { transform: 'translateY(-20px)', opacity: '0' },
'100%': { transform: 'translateY(0)', opacity: '1' },
},
scaleIn: {
'0%': { transform: 'scale(0.9)', opacity: '0' },
'100%': { transform: 'scale(1)', opacity: '1' },
},
shimmer: {
'0%': { transform: 'translateX(-100%)' },
'100%': { transform: 'translateX(100%)' },
},
},
},
},
}
```
---
## 4. Standards, Compliance & Security
### 4.1 International Standards
- **WCAG 2.1 Level AA** - Animation ต้องไม่ทำให้ผู้ใช้สับสนหรือเกิดอาการวิงเวียน
- **ISO 9241-11** - Usability Standards สำหรับ Animation
- **WAI-ARIA** - Accessibility สำหรับ Animated Components
### 4.2 Security Protocol
Animation Libraries โดยทั่วไปไม่มีปัญหาด้านความปลอดภัยโดยตรง แต่ต้องระวัง:
1. **XSS Prevention** - ไม่ใช้ user input ใน animation parameters โดยตรง
2. **Performance DoS** - Animation ที่ซับซ้อนเกินไปอาจทำให้เบราว์เซอร์หยุดทำงาน
3. **Memory Leaks** - ต้อง cleanup animations เมื่อ component unmount
### 4.3 Explainability
Animation ต้องสามารถอธิบายได้ว่า:
1. **Purpose** - Animation นี้มีวัตถุประสงค์อะไร
2. **Trigger** - Animation เริ่มทำงานเมื่อไร
3. **Duration** - Animation ใช้เวลานานแค่ไหน
4. **Accessibility** - Animation นี้เคารพค่ากำหนดของผู้ใช้หรือไม่
---
## 5. Unit Economics & Performance Metrics (KPIs)
### 5.1 Cost Calculation
| Metric | Calculation | Target |
|--------|-------------|--------|
| Animation Bundle Size | Sum of animation libraries | < 100 KB (gzipped) |
| First Contentful Paint (FCP) | Time to first paint | < 1.8s |
| Time to Interactive (TTI) | Time to full interactivity | < 3.8s |
| Cumulative Layout Shift (CLS) | Layout stability score | < 0.1 |
| Animation Frame Rate | FPS during animation | > 55 FPS |
### 5.2 Key Performance Indicators
**Performance Metrics:**
1. **Frame Rate** - Animation ต้องรันที่ 60 FPS หรือมากกว่า
2. **Main Thread Blocking** - Animation ไม่ควร block main thread เกิน 50ms
3. **Memory Usage** - Animation ไม่ควรใช้ memory เกิน 50 MB เพิ่มขึ้น
4. **GPU Acceleration** - Animation ต้องใช้ GPU-accelerated properties
**Business Metrics:**
1. **Conversion Rate** - เพิ่มขึ้น 10-20% หลังใช้ Animation ที่ดี
2. **Bounce Rate** - ลดลง 15-25%
3. **User Engagement** - เพิ่มขึ้น 20-30%
4. **Support Tickets** - ลดลง 10-15% จากการใช้ Onboarding Animations
---
## 6. Strategic Recommendations (CTO Insights)
### 6.1 Phase Rollout
**Phase 1: Foundation (Week 1-2)**
- Setup Animation Libraries (Framer Motion, GSAP, React Spring)
- Create Base Animation Components
- Implement Accessibility Hooks
- Setup Performance Monitoring
**Phase 2: Core Patterns (Week 3-4)**
- Implement Page Transitions
- Create Loading Skeletons
- Build Toast Notification System
- Add Accordion Components
**Phase 3: Advanced Features (Week 5-6)**
- Implement Scroll Animations
- Add Gesture Animations
- Create Layout Animations
- Build Animation System
**Phase 4: Optimization (Week 7-8)**
- Performance Audit
- A/B Testing
- Accessibility Review
- Documentation
### 6.2 Pitfalls to Avoid
1. **Over-animating** - Animation มากเกินไปทำให้ผู้ใช้รำคาญ
2. **Ignoring Accessibility** - ไม่เคารพค่า `prefers-reduced-motion`
3. **Performance Issues** - ใช้ properties ที่ไม่ใช่ GPU-accelerated
4. **Inconsistent Timing** - Animation durations ไม่สอดคล้องกัน
5. **No Fallback** - ไม่มี fallback สำหรับ browsers ที่ไม่รองรับ
### 6.3 Best Practices Checklist
- [ ] ใช้ GPU-accelerated properties (transform, opacity)
- [ ] เคารพค่า `prefers-reduced-motion`
- [ ] Animation duration อยู่ในช่วง 200-500ms
- [ ] ใช้ easing functions ที่เหมาะสม
- [ ] Test บน devices และ browsers หลายแบบ
- [ ] Monitor performance metrics
- [ ] A/B test animation variations
- [ ] Document animation patterns
- [ ] Create reusable animation components
- [ ] Implement animation cleanup
---
## 7. Implementation Examples
### 7.1 CSS Animations
#### Basic Transitions
```css
/* styles.css */
.button {
background-color: #3b82f6;
transition: background-color 0.3s ease;
}
.button:hover {
background-color: #1d4ed8;
}
/* Multiple properties */
.card {
transform: translateY(0);
opacity: 1;
transition: transform 0.3s ease, opacity 0.3s ease;
}
.card:hover {
transform: translateY(-4px);
opacity: 0.9;
}
/* Using transition shorthand */
.element {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
```
#### Keyframe Animations
```css
/* Fade in animation */
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.fade-in {
animation: fadeIn 0.5s ease-in-out;
}
/* Slide up animation */
@keyframes slideUp {
from {
transform: translateY(20px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.slide-up {
animation: slideUp 0.4s ease-out;
}
/* Bounce animation */
@keyframes bounce {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-10px);
}
}
.bounce {
animation: bounce 1s ease-in-out infinite;
}
/* Pulse animation */
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
.pulse {
animation: pulse 2s ease-in-out infinite;
}
/* Spin animation */
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.spin {
animation: spin 1s linear infinite;
}
/* Shake animation */
@keyframes shake {
0%, 100% {
transform: translateX(0);
}
10%, 30%, 50%, 70%, 90% {
transform: translateX(-5px);
}
20%, 40%, 60%, 80% {
transform: translateX(5px);
}
}
.shake {
animation: shake 0.5s ease-in-out;
}
```
#### CSS Animation with React
```typescript
// components/AnimatedComponent.tsx
"use client"
import { useState } from "react"
import styles from "./AnimatedComponent.module.css"
export function AnimatedComponent() {
const [isVisible, setIsVisible] = useState(false)
return (
Animated Content
)
}
// AnimatedComponent.module.css
/*
.box {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.3s ease, transform 0.3s ease;
}
.box.visible {
opacity: 1;
transform: translateY(0);
}
*/
```
#### Tailwind CSS Animations
```typescript
// Using Tailwind built-in animations
export function TailwindAnimations() {
return (
{/* Spin */}
{/* Ping */}
{/* Pulse */}
{/* Bounce */}
)
}
```
### 7.2 Framer Motion
#### Installation
```bash
npm install framer-motion
```
#### Basic Animations
```typescript
"use client"
import { motion } from "framer-motion"
// Fade in animation
export function FadeIn() {
return (
Fade In Content
)
}
// Slide animation
export function SlideIn() {
return (
Slide In Content
)
}
// Scale animation
export function ScaleIn() {
return (
Scale In Content
)
}
// Multiple properties
export function ComplexAnimation() {
return (
Complex Animation
)
}
```
#### Hover and Tap Animations
```typescript
"use client"
import { motion } from "framer-motion"
export function InteractiveButton() {
return (
Click Me
)
}
export function HoverCard() {
return (
Hover Card
)
}
export function AnimatedIcon() {
return (
)
}
```
#### Variants
```typescript
"use client"
import { motion } from "framer-motion"
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
},
},
}
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.4 },
},
}
export function StaggeredList({ items }: { items: string[] }) {
return (
{items.map((item, index) => (
{item}
))}
)
}
// Card variants with hover
const cardVariants = {
initial: { scale: 1 },
hover: { scale: 1.05 },
tap: { scale: 0.98 },
}
export function VariantCard() {
return (
Interactive Card
)
}
```
#### AnimatePresence for Exit Animations
```typescript
"use client"
import { useState } from "react"
import { motion, AnimatePresence } from "framer-motion"
export function ToggleContent() {
const [isVisible, setIsVisible] = useState(true)
return (
{isVisible && (
Content that animates in and out
)}
)
}
// Modal with AnimatePresence
export function AnimatedModal({
isOpen,
onClose,
children
}: {
isOpen: boolean
onClose: () => void
children: React.ReactNode
}) {
return (
{isOpen && (
<>
{/* Backdrop */}
{/* Modal */}
{children}
>
)}
)
}
// List with remove animation
export function AnimatedList() {
const [items, setItems] = useState([1, 2, 3, 4, 5])
const removeItem = (id: number) => {
setItems(items.filter(item => item !== id))
}
return (
{items.map(item => (
Item {item}
))}
)
}
```
#### Layout Animations
```typescript
"use client"
import { useState } from "react"
import { motion, LayoutGroup } from "framer-motion"
export function ExpandableCard() {
const [isExpanded, setIsExpanded] = useState(false)
return (
setIsExpanded(!isExpanded)}
className="bg-white rounded-lg shadow cursor-pointer overflow-hidden"
style={{ width: isExpanded ? 400 : 200 }}
>
Card Title
{isExpanded && (
Expanded content goes here...
)}
)
}
// Shared layout animation
export function TabsWithAnimation() {
const [activeTab, setActiveTab] = useState(0)
const tabs = ["Home", "About", "Contact"]
return (
{tabs.map((tab, index) => (
))}
)
}
// Reorderable list
import { Reorder } from "framer-motion"
export function ReorderableList() {
const [items, setItems] = useState(["Item 1", "Item 2", "Item 3", "Item 4"])
return (
{items.map(item => (
{item}
))}
)
}
```
#### Scroll Animations
```typescript
"use client"
import { motion, useScroll, useTransform, useInView } from "framer-motion"
import { useRef } from "react"
// Scroll progress indicator
export function ScrollProgress() {
const { scrollYProgress } = useScroll()
return (
)
}
// Parallax effect
export function ParallaxSection() {
const ref = useRef(null)
const { scrollYProgress } = useScroll({
target: ref,
offset: ["start end", "end start"],
})
const y = useTransform(scrollYProgress, [0, 1], [100, -100])
const opacity = useTransform(scrollYProgress, [0, 0.5, 1], [0, 1, 0])
return (
Parallax Content
)
}
// Animate when in view
export function AnimateOnScroll({ children }: { children: React.ReactNode }) {
const ref = useRef(null)
const isInView = useInView(ref, { once: true, margin: "-100px" })
return (
{children}
)
}
// Scroll-triggered animation with variants
const scrollVariants = {
hidden: { opacity: 0, y: 75 },
visible: { opacity: 1, y: 0 },
}
export function ScrollReveal({ children }: { children: React.ReactNode }) {
const ref = useRef(null)
const isInView = useInView(ref, { once: true })
return (
{children}
)
}
```
#### Gesture Animations
```typescript
"use client"
import { motion, useDragControls } from "framer-motion"
// Draggable element
export function DraggableBox() {
return (
)
}
// Drag with snap back
export function SnapBackDrag() {
return (
)
}
// Swipe to delete
export function SwipeToDelete({ onDelete }: { onDelete: () => void }) {
return (
{
if (info.offset.x < -80) {
onDelete()
}
}}
className="p-4 bg-white rounded shadow"
>
Swipe left to delete
)
}
// Pan gesture
export function PanGesture() {
return (
{
console.log("Pan:", info.delta.x, info.delta.y)
}}
onPanEnd={(_, info) => {
console.log("Pan ended:", info.velocity.x, info.velocity.y)
}}
className="w-48 h-48 bg-gray-200 rounded-lg touch-none"
/>
)
}
```
### 7.3 GSAP (GreenSock Animation Platform)
#### Installation
```bash
npm install gsap
```
#### Basic Animations
```typescript
"use client"
import { useRef, useEffect } from "react"
import gsap from "gsap"
export function GSAPBasic() {
const boxRef = useRef(null)
useEffect(() => {
if (boxRef.current) {
gsap.to(boxRef.current, {
x: 200,
rotation: 360,
duration: 2,
ease: "power2.out",
})
}
}, [])
return (
)
}
// Multiple targets
export function GSAPMultiple() {
const containerRef = useRef(null)
useEffect(() => {
if (containerRef.current) {
gsap.to(containerRef.current.children, {
y: 0,
opacity: 1,
stagger: 0.2,
duration: 0.6,
ease: "power3.out",
})
}
}, [])
return (
{[1, 2, 3, 4].map(i => (
Item {i}
))}
)
}
```
#### GSAP Timeline
```typescript
"use client"
import { useRef, useEffect } from "react"
import gsap from "gsap"
export function GSAPTimeline() {
const boxRef = useRef(null)
const circleRef = useRef(null)
useEffect(() => {
const tl = gsap.timeline({ defaults: { duration: 0.5 } })
tl.to(boxRef.current, { x: 100 })
.to(boxRef.current, { y: 100 })
.to(boxRef.current, { rotation: 180 })
.to(circleRef.current, { scale: 1.5 }, "<") // Start at same time as previous
.to(circleRef.current, { backgroundColor: "#ef4444" }, "+=0.2") // Start 0.2s after previous
return () => {
tl.kill()
}
}, [])
return (
)
}
// Timeline with labels
export function GSAPTimelineLabels() {
const containerRef = useRef(null)
useEffect(() => {
const tl = gsap.timeline()
tl.addLabel("start")
.to(".box-1", { x: 100, duration: 0.5 })
.addLabel("middle")
.to(".box-2", { x: 100, duration: 0.5 })
.addLabel("end")
.to(".box-3", { x: 100, duration: 0.5 })
// Jump to label
// tl.play("middle")
return () => {
tl.kill()
}
}, [])
return (
)
}
```
#### GSAP ScrollTrigger
```typescript
"use client"
import { useRef, useEffect } from "react"
import gsap from "gsap"
import { ScrollTrigger } from "gsap/ScrollTrigger"
gsap.registerPlugin(ScrollTrigger)
export function GSAPScrollTrigger() {
const sectionRef = useRef(null)
useEffect(() => {
const ctx = gsap.context(() => {
gsap.from(".animate-item", {
y: 100,
opacity: 0,
stagger: 0.2,
duration: 1,
scrollTrigger: {
trigger: sectionRef.current,
start: "top 80%",
end: "bottom 20%",
toggleActions: "play none none reverse",
},
})
}, sectionRef)
return () => ctx.revert()
}, [])
return (
{[1, 2, 3, 4].map(i => (
Section Item {i}
))}
)
}
// Scroll-linked animation
export function GSAPScrollLinked() {
const containerRef = useRef(null)
useEffect(() => {
const ctx = gsap.context(() => {
gsap.to(".progress-bar", {
width: "100%",
ease: "none",
scrollTrigger: {
trigger: containerRef.current,
start: "top top",
end: "bottom bottom",
scrub: true,
},
})
}, containerRef)
return () => ctx.revert()
}, [])
return (
)
}
// Pin section
export function GSAPPinSection() {
const sectionRef = useRef(null)
useEffect(() => {
const ctx = gsap.context(() => {
ScrollTrigger.create({
trigger: sectionRef.current,
start: "top top",
end: "+=500",
pin: true,
pinSpacing: true,
})
}, sectionRef)
return () => ctx.revert()
}, [])
return (
)
}
```
#### GSAP with React Hooks
```typescript
"use client"
import { useRef, useEffect, useLayoutEffect } from "react"
import gsap from "gsap"
// Custom hook for GSAP animations
function useGSAP(callback: (ctx: gsap.Context) => void, deps: any[] = []) {
const ref = useRef(null)
useLayoutEffect(() => {
const ctx = gsap.context(() => {
callback(ctx)
}, ref)
return () => ctx.revert()
}, deps)
return ref
}
// Usage
export function GSAPHookExample() {
const containerRef = useGSAP((ctx) => {
gsap.from(".box", {
y: 50,
opacity: 0,
stagger: 0.1,
duration: 0.5,
})
})
return (
{[1, 2, 3].map(i => (
Box {i}
))}
)
}
// Responsive animation hook
function useResponsiveGSAP() {
const ref = useRef(null)
useEffect(() => {
const mm = gsap.matchMedia()
mm.add("(min-width: 768px)", () => {
gsap.to(ref.current, { x: 200 })
})
mm.add("(max-width: 767px)", () => {
gsap.to(ref.current, { y: 100 })
})
return () => mm.revert()
}, [])
return ref
}
```
### 7.4 React Spring
#### Installation
```bash
npm install @react-spring/web
```
#### Basic Animations
```typescript
"use client"
import { useSpring, animated } from "@react-spring/web"
// Simple animation
export function SpringBasic() {
const springs = useSpring({
from: { opacity: 0, y: 20 },
to: { opacity: 1, y: 0 },
})
return (
Animated Content
)
}
// Animation with config
export function SpringWithConfig() {
const springs = useSpring({
from: { scale: 0 },
to: { scale: 1 },
config: { tension: 200, friction: 12 },
})
return (
`scale(${s})`),
}}
className="w-24 h-24 bg-blue-500 rounded-lg"
/>
)
}
// Toggle animation
export function SpringToggle() {
const [isOpen, setIsOpen] = useState(false)
const springs = useSpring({
height: isOpen ? 200 : 0,
opacity: isOpen ? 1 : 0,
config: { tension: 300, friction: 20 },
})
return (
Expandable content
)
}
```
#### useTransition for Lists
```typescript
"use client"
import { useState } from "react"
import { useTransition, animated } from "@react-spring/web"
export function SpringList() {
const [items, setItems] = useState([1, 2, 3])
const transitions = useTransition(items, {
from: { opacity: 0, x: -20 },
enter: { opacity: 1, x: 0 },
leave: { opacity: 0, x: 20 },
keys: item => item,
})
const addItem = () => {
setItems([...items, items.length + 1])
}
const removeItem = (id: number) => {
setItems(items.filter(item => item !== id))
}
return (
{transitions((style, item) => (
Item {item}
))}
)
}
// Page transitions
export function PageTransition({ children, key }: { children: React.ReactNode; key: string }) {
const transitions = useTransition(key, {
from: { opacity: 0, transform: "translateX(20px)" },
enter: { opacity: 1, transform: "translateX(0)" },
leave: { opacity: 0, transform: "translateX(-20px)" },
})
return transitions((style, item) => (
{children}
))
}
```
#### useChain for Sequenced Animations
```typescript
"use client"
import { useRef } from "react"
import { useSpring, useTrail, useChain, animated, SpringRef } from "@react-spring/web"
export function ChainedAnimation() {
const springRef = useRef(null)
const trailRef = useRef(null)
const containerSpring = useSpring({
ref: springRef,
from: { scale: 0 },
to: { scale: 1 },
})
const items = [1, 2, 3, 4]
const trail = useTrail(items.length, {
ref: trailRef,
from: { opacity: 0, y: 20 },
to: { opacity: 1, y: 0 },
})
useChain([springRef, trailRef], [0, 0.3])
return (
`scale(${s})`),
}}
className="p-6 bg-white rounded-lg shadow"
>
{trail.map((style, index) => (
Item {items[index]}
))}
)
}
```
#### useSprings for Multiple Elements
```typescript
"use client"
import { useState } from "react"
import { useSprings, animated } from "@react-spring/web"
export function MultipleElements() {
const [active, setActive] = useState(-1)
const items = [0, 1, 2, 3, 4]
const springs = useSprings(
items.length,
items.map((_, i) => ({
scale: active === i ? 1.2 : 1,
opacity: active === -1 || active === i ? 1 : 0.5,
config: { tension: 300, friction: 20 },
}))
)
return (
{springs.map((style, i) => (
`scale(${s})`),
opacity: style.opacity,
}}
onMouseEnter={() => setActive(i)}
onMouseLeave={() => setActive(-1)}
className="w-16 h-16 bg-blue-500 rounded cursor-pointer"
/>
))}
)
}
```
### 7.5 Performance Considerations
#### Optimize CSS Animations
```css
/* Use transform and opacity for best performance */
.good-animation {
transform: translateX(100px);
opacity: 0.5;
/* These properties are GPU-accelerated */
}
/* Avoid animating layout properties */
.bad-animation {
width: 200px;
height: 200px;
margin-left: 100px;
/* These trigger layout recalculations */
}
/* Use will-change sparingly */
.will-animate {
will-change: transform, opacity;
}
/* Remove will-change after animation */
.animation-complete {
will-change: auto;
}
```
#### React Animation Performance
```typescript
"use client"
import { memo, useMemo } from "react"
import { motion } from "framer-motion"
// Memoize animated components
const AnimatedCard = memo(function AnimatedCard({ item }: { item: { id: number; title: string } }) {
return (
{item.title}
)
})
// Use useMemo for animation variants
export function OptimizedAnimations() {
const variants = useMemo(() => ({
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 },
}), [])
return (
Content
)
}
// Reduce motion for accessibility
export function ReducedMotion() {
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches
return (
Respects user preference
)
}
```
#### Lazy Load Animation Libraries
```typescript
"use client"
import dynamic from "next/dynamic"
import { Suspense } from "react"
// Lazy load heavy animation components
const HeavyAnimation = dynamic(() => import("./HeavyAnimation"), {
loading: () => Loading...
,
ssr: false,
})
export function LazyAnimatedSection() {
return (
Loading animation...}>
)
}
```
### 7.6 Accessibility in Animations
#### Respecting User Preferences
```typescript
"use client"
import { useEffect, useState } from "react"
import { motion, useReducedMotion } from "framer-motion"
// Using Framer Motion's built-in hook
export function AccessibleAnimation() {
const prefersReducedMotion = useReducedMotion()
return (
Accessible animated content
)
}
// Custom hook for reduced motion
function usePreferReducedMotion() {
const [prefersReducedMotion, setPrefersReducedMotion] = useState(false)
useEffect(() => {
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)")
setPrefersReducedMotion(mediaQuery.matches)
const listener = (event: MediaQueryListEvent) => {
setPrefersReducedMotion(event.matches)
}
mediaQuery.addEventListener("change", listener)
return () => mediaQuery.removeEventListener("change", listener)
}, [])
return prefersReducedMotion
}
// Usage
export function CustomAccessibleAnimation() {
const prefersReducedMotion = usePreferReducedMotion()
return (
Content respects user preference
)
}
```
#### Focus Management
```typescript
"use client"
import { useRef, useEffect } from "react"
import { motion, AnimatePresence } from "framer-motion"
export function AccessibleModal({
isOpen,
onClose,
children,
}: {
isOpen: boolean
onClose: () => void
children: React.ReactNode
}) {
const closeButtonRef = useRef(null)
useEffect(() => {
if (isOpen) {
closeButtonRef.current?.focus()
}
}, [isOpen])
return (
{isOpen && (
{children}
)}
)
}
```
#### ARIA Live Regions
```typescript
"use client"
import { useState } from "react"
import { motion, AnimatePresence } from "framer-motion"
export function NotificationWithAria() {
const [notifications, setNotifications] = useState([])
const addNotification = (message: string) => {
setNotifications([...notifications, message])
setTimeout(() => {
setNotifications(prev => prev.slice(1))
}, 3000)
}
return (
{notifications.map((notification, index) => (
{notification}
))}
)
}
```
### 7.7 Common Animation Patterns
#### Page Transitions
```typescript
"use client"
import { motion } from "framer-motion"
const pageVariants = {
initial: { opacity: 0, x: -20 },
enter: { opacity: 1, x: 0 },
exit: { opacity: 0, x: 20 },
}
export function PageWrapper({ children }: { children: React.ReactNode }) {
return (
{children}
)
}
```
#### Loading Skeletons
```typescript
"use client"
export function SkeletonLoader() {
return (
)
}
// Shimmer effect
export function ShimmerSkeleton() {
return (
)
}
```
#### Notification Toast
```typescript
"use client"
import { useState } from "react"
import { motion, AnimatePresence } from "framer-motion"
interface Toast {
id: number
message: string
type: "success" | "error" | "info"
}
export function useToast() {
const [toasts, setToasts] = useState([])
const addToast = (message: string, type: Toast["type"] = "info") => {
const id = Date.now()
setToasts(prev => [...prev, { id, message, type }])
setTimeout(() => {
setToasts(prev => prev.filter(t => t.id !== id))
}, 3000)
}
const ToastContainer = () => (
{toasts.map(toast => (
{toast.message}
))}
)
return { addToast, ToastContainer }
}
```
#### Accordion
```typescript
"use client"
import { useState } from "react"
import { motion, AnimatePresence } from "framer-motion"
interface AccordionItemProps {
title: string
children: React.ReactNode
isOpen: boolean
onToggle: () => void
}
function AccordionItem({ title, children, isOpen, onToggle }: AccordionItemProps) {
return (
{isOpen && (
{children}
)}
)
}
export function Accordion({ items }: { items: { title: string; content: string }[] }) {
const [openIndex, setOpenIndex] = useState(null)
return (
{items.map((item, index) => (
setOpenIndex(openIndex === index ? null : index)}
>
{item.content}
))}
)
}
```
#### Animated Counter
```typescript
"use client"
import { useEffect, useState } from "react"
import { useSpring, animated } from "@react-spring/web"
export function AnimatedCounter({ value }: { value: number }) {
const { number } = useSpring({
from: { number: 0 },
number: value,
delay: 200,
config: { mass: 1, tension: 20, friction: 10 },
})
return (
{number.to(n => n.toFixed(0))}
)
}
// With Framer Motion
import { motion, useMotionValue, useTransform, animate } from "framer-motion"
export function FramerCounter({ value }: { value: number }) {
const count = useMotionValue(0)
const rounded = useTransform(count, Math.round)
useEffect(() => {
const animation = animate(count, value, { duration: 2 })
return animation.stop
}, [value])
return {rounded}
}
```
### 7.8 Animation Libraries Comparison
| Feature | CSS | Framer Motion | GSAP | React Spring |
|---------|-----|---------------|------|--------------|
| Bundle Size | 0 KB | ~30 KB | ~60 KB | ~20 KB |
| Learning Curve | Low | Medium | Medium-High | Medium |
| Performance | Excellent | Good | Excellent | Good |
| React Integration | Manual | Native | Manual | Native |
| Gesture Support | Limited | Excellent | Good | Good |
| Layout Animation | No | Yes | Manual | Limited |
| Exit Animation | No | Yes | Manual | Yes |
| Physics-based | No | Yes | Plugin | Yes |
| Timeline Control | Limited | Limited | Excellent | Limited |
| Best For | Simple animations | React apps | Complex animations | Spring physics |