# Performance Optimization

Best practices for 60fps animations in React.

## Table of Contents
1. [Core Principles](#core-principles)
2. [GPU Acceleration](#gpu-acceleration)
3. [ScrollTrigger Optimization](#scrolltrigger-optimization)
4. [Memory Management](#memory-management)
5. [Lazy Loading](#lazy-loading)
6. [Monitoring & Debugging](#monitoring--debugging)
7. [Mobile Optimization](#mobile-optimization)

## Core Principles

### The Golden Rules

1. **Only animate `transform` and `opacity`** - These are GPU-accelerated
2. **Avoid animating layout properties** - `width`, `height`, `top`, `left`, `margin`, `padding` trigger reflow
3. **Use `will-change` sparingly** - Apply before animation, remove after
4. **Batch DOM reads/writes** - Avoid forced synchronous layouts
5. **Kill unused animations** - Clean up ScrollTriggers on unmount

### What Triggers What

| Property | Paint | Layout | Composite |
|----------|-------|--------|-----------|
| `transform` | No | No | Yes |
| `opacity` | No | No | Yes |
| `filter` | Yes | No | Yes |
| `width/height` | Yes | Yes | Yes |
| `top/left` | Yes | Yes | Yes |
| `background-color` | Yes | No | Yes |

## GPU Acceleration

### Force GPU Layer

```tsx
// Good: Use transform for positioning
gsap.to(element, { x: 100, y: 50 })

// Bad: Avoid top/left
gsap.to(element, { top: 100, left: 50 })
```

### will-change

```tsx
function AnimatedElement() {
  const ref = useRef<HTMLDivElement>(null)

  useGSAP(() => {
    // Set will-change before animation
    gsap.set(ref.current, { willChange: 'transform' })

    gsap.to(ref.current, {
      x: 100,
      duration: 1,
      onComplete: () => {
        // Remove after animation
        gsap.set(ref.current, { willChange: 'auto' })
      }
    })
  })

  return <div ref={ref}>Animated</div>
}
```

### CSS for GPU Promotion

```css
/* Force GPU layer for frequently animated elements */
.gpu-accelerated {
  transform: translateZ(0);
  backface-visibility: hidden;
}

/* Or use will-change strategically */
.about-to-animate {
  will-change: transform, opacity;
}
```

## ScrollTrigger Optimization

### Global Configuration

```tsx
// Configure once in your app
ScrollTrigger.config({
  limitCallbacks: true,        // Limit callback frequency
  ignoreMobileResize: true,    // Ignore mobile address bar resize
})

// Normalize scroll behavior
ScrollTrigger.normalizeScroll(true)
```

### Batch Similar Animations

```tsx
// Bad: Individual ScrollTriggers
items.forEach(item => {
  gsap.from(item, {
    opacity: 0,
    scrollTrigger: { trigger: item }
  })
})

// Good: Batch them
ScrollTrigger.batch('.batch-item', {
  onEnter: (elements) => {
    gsap.from(elements, { opacity: 0, stagger: 0.1 })
  }
})
```

### Lazy ScrollTrigger Creation

```tsx
function LazySection({ children }: { children: React.ReactNode }) {
  const ref = useRef<HTMLDivElement>(null)
  const [isNearViewport, setIsNearViewport] = useState(false)

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setIsNearViewport(true)
          observer.disconnect()
        }
      },
      { rootMargin: '200px' } // Start 200px before visible
    )

    if (ref.current) observer.observe(ref.current)
    return () => observer.disconnect()
  }, [])

  useGSAP(() => {
    if (!isNearViewport) return

    // Create ScrollTrigger only when near viewport
    gsap.from('.lazy-item', {
      opacity: 0,
      y: 50,
      scrollTrigger: {
        trigger: ref.current,
        start: 'top 80%',
      }
    })
  }, { dependencies: [isNearViewport], scope: ref })

  return <div ref={ref}>{children}</div>
}
```

### Reduce Marker Overhead

```tsx
// Always disable markers in production
const isDev = process.env.NODE_ENV === 'development'

gsap.to('.element', {
  scrollTrigger: {
    markers: isDev,  // Only in development
  }
})
```

## Memory Management

### Cleanup ScrollTriggers

```tsx
// useGSAP handles this automatically, but for manual cases:
useEffect(() => {
  const st = ScrollTrigger.create({
    trigger: '.element',
    onEnter: () => console.log('enter')
  })

  return () => st.kill()  // Important!
}, [])
```

### Kill All on Route Change

```tsx
// In your layout or a global component
'use client'

import { usePathname } from 'next/navigation'
import { useEffect } from 'react'
import { ScrollTrigger } from 'gsap/ScrollTrigger'

export function ScrollTriggerCleanup() {
  const pathname = usePathname()

  useEffect(() => {
    return () => {
      // Kill all ScrollTriggers on route change
      ScrollTrigger.getAll().forEach(st => st.kill())
    }
  }, [pathname])

  return null
}
```

### SplitText Cleanup

```tsx
function AnimatedText({ text }: { text: string }) {
  const textRef = useRef<HTMLParagraphElement>(null)
  const splitRef = useRef<SplitText | null>(null)

  useGSAP(() => {
    splitRef.current = new SplitText(textRef.current, { type: 'chars' })

    gsap.from(splitRef.current.chars, { opacity: 0, stagger: 0.02 })

    // Cleanup split on unmount
    return () => splitRef.current?.revert()
  })

  return <p ref={textRef}>{text}</p>
}
```

### Motion Cleanup

```tsx
// Motion handles most cleanup automatically
// But for manual animations:

function MotionComponent() {
  useEffect(() => {
    const controls = animate('.element', { x: 100 })

    return () => controls.stop()
  }, [])
}
```

## Lazy Loading

### Images with Reveal

```tsx
function LazyImage({ src, alt }: { src: string; alt: string }) {
  const [loaded, setLoaded] = useState(false)
  const imgRef = useRef<HTMLImageElement>(null)
  const containerRef = useRef<HTMLDivElement>(null)

  useGSAP(() => {
    if (!loaded) return

    gsap.from(containerRef.current, {
      clipPath: 'inset(100% 0% 0% 0%)',
      duration: 1.2,
      ease: 'power4.inOut',
    })

    gsap.from(imgRef.current, {
      scale: 1.3,
      duration: 1.5,
      ease: 'power2.out',
    })
  }, { dependencies: [loaded] })

  return (
    <div ref={containerRef} className="overflow-hidden">
      <img
        ref={imgRef}
        src={src}
        alt={alt}
        loading="lazy"
        onLoad={() => setLoaded(true)}
        className={loaded ? 'opacity-100' : 'opacity-0'}
      />
    </div>
  )
}
```

### Code Splitting Animations

```tsx
// Dynamically import heavy animation libraries
const ThreeScene = dynamic(
  () => import('@/components/ThreeScene'),
  {
    loading: () => <div>Loading 3D...</div>,
    ssr: false
  }
)
```

## Monitoring & Debugging

### Chrome DevTools Performance Tab

1. Open DevTools (F12)
2. Go to Performance tab
3. Click record, scroll, stop
4. Look for:
   - Long frames (red bars)
   - Layout thrashing
   - Excessive paint areas

### GSAP DevTools

```tsx
// Install GSAP DevTools (Club GSAP)
import { GSDevTools } from 'gsap/GSDevTools'
gsap.registerPlugin(GSDevTools)

// Add to your dev environment
if (process.env.NODE_ENV === 'development') {
  GSDevTools.create({ animation: masterTimeline })
}
```

### Performance Markers

```tsx
function measureAnimation() {
  performance.mark('animation-start')

  gsap.to('.element', {
    x: 100,
    onComplete: () => {
      performance.mark('animation-end')
      performance.measure('animation', 'animation-start', 'animation-end')
      console.log(performance.getEntriesByName('animation'))
    }
  })
}
```

### FPS Monitor

```tsx
'use client'

import { useEffect, useState } from 'react'

export function FPSMonitor() {
  const [fps, setFps] = useState(0)

  useEffect(() => {
    let frameCount = 0
    let lastTime = performance.now()

    const loop = () => {
      frameCount++
      const now = performance.now()

      if (now - lastTime >= 1000) {
        setFps(frameCount)
        frameCount = 0
        lastTime = now
      }

      requestAnimationFrame(loop)
    }

    const id = requestAnimationFrame(loop)
    return () => cancelAnimationFrame(id)
  }, [])

  return (
    <div className="fixed top-4 right-4 bg-black text-white p-2 z-50">
      {fps} FPS
    </div>
  )
}
```

## Mobile Optimization

### Detect Low-End Devices

```tsx
function useIsLowEndDevice() {
  const [isLowEnd, setIsLowEnd] = useState(false)

  useEffect(() => {
    // Check for low-end indicators
    const memory = (navigator as any).deviceMemory
    const cores = navigator.hardwareConcurrency

    if (memory && memory < 4) setIsLowEnd(true)
    if (cores && cores < 4) setIsLowEnd(true)

    // Check for battery saver
    if ('getBattery' in navigator) {
      (navigator as any).getBattery().then((battery: any) => {
        if (battery.charging === false && battery.level < 0.2) {
          setIsLowEnd(true)
        }
      })
    }
  }, [])

  return isLowEnd
}
```

### Reduce Motion for Low-End

```tsx
function OptimizedAnimation() {
  const isLowEnd = useIsLowEndDevice()
  const prefersReducedMotion = useReducedMotion()

  const shouldSimplify = isLowEnd || prefersReducedMotion

  useGSAP(() => {
    if (shouldSimplify) {
      // Simple fade
      gsap.from('.element', { opacity: 0, duration: 0.3 })
    } else {
      // Full animation
      gsap.from('.element', {
        opacity: 0,
        y: 50,
        rotation: 10,
        duration: 0.8,
        ease: 'back.out(1.7)'
      })
    }
  }, [shouldSimplify])
}
```

### Touch-Friendly Timing

```tsx
// Faster animations on mobile (no hover states)
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent)

const duration = isMobile ? 0.3 : 0.5
const stagger = isMobile ? 0.05 : 0.1
```

### Disable Smooth Scroll on Low-End

```tsx
function SmartSmoothScroll({ children }: { children: React.ReactNode }) {
  const isLowEnd = useIsLowEndDevice()

  if (isLowEnd) {
    // Skip Lenis on low-end devices
    return <>{children}</>
  }

  return (
    <ReactLenis root options={{ lerp: 0.1 }}>
      {children}
    </ReactLenis>
  )
}
```

## Quick Reference

### Performance Checklist

- [ ] Only animating transform/opacity
- [ ] ScrollTriggers cleaned up on unmount
- [ ] Batch animations where possible
- [ ] Lazy load heavy components
- [ ] Test on real mobile devices
- [ ] Check for memory leaks
- [ ] Remove markers in production
- [ ] Respect prefers-reduced-motion
- [ ] Test on low-end devices
