Skip to main content

GSAP Setup

Music Store uses GSAP (GreenSock Animation Platform) for all animations. GSAP is installed as a dependency and imported where needed.

Installation

package.json
{
  "dependencies": {
    "gsap": "^3.14.2"
  }
}

Basic Import

import gsap from "gsap";

Animation Patterns

Using gsap.context()

All animations in Music Store use gsap.context() for proper cleanup and scoping. This is the recommended pattern:
import { useEffect, useRef } from "react";
import gsap from "gsap";

function MyComponent() {
  const elementRef = useRef(null);

  useEffect(() => {
    const ctx = gsap.context(() => {
      // All animations here
      gsap.to(elementRef.current, {
        opacity: 1,
        duration: 1
      });
    });

    // Cleanup: revert all animations
    return () => ctx.revert();
  }, []);

  return <div ref={elementRef}>Content</div>;
}
Why use gsap.context()?
  • Automatic cleanup when component unmounts
  • Prevents memory leaks
  • Scopes animations to the component
  • Reverts all animations in one call

Real Examples from Music Store

Hero Component Timeline

The Hero component uses a GSAP timeline to sequence multiple animations:
import { useEffect, useRef } from "react";
import gsap from "gsap";

function Hero() {
  const titleRef = useRef(null);
  const textRef = useRef(null);
  const btnRef = useRef(null);

  useEffect(() => {
    const ctx = gsap.context(() => {
      gsap.timeline({ defaults: { ease: "power3.out" } })
        .fromTo(titleRef.current,
          { y: 50, opacity: 0 },
          { y: 0, opacity: 1, duration: 1.5 }
        )
        .fromTo(textRef.current,
          { y: 30, opacity: 0 },
          { y: 0, opacity: 1, duration: 1.2 },
          "-=1"  // Start 1 second before previous animation ends
        )
        .fromTo(btnRef.current,
          { y: 20, opacity: 0 },
          { y: 0, opacity: 1, duration: 1 },
          "-=0.8"  // Overlap by 0.8 seconds
        );
    });

    return () => ctx.revert();
  }, []);

  return (
    <section className="relative h-screen flex text-white">
      <div ref={titleRef}>
        <h1>Music Store</h1>
      </div>
      
      <div ref={textRef}>
        <p>Find your perfect sound</p>
      </div>
      
      <button ref={btnRef}>
        Shop Now
      </button>
    </section>
  );
}
Timeline benefits:
  • Sequence multiple animations
  • Control timing with overlap (-= notation)
  • Set default easing for all animations
  • Easier to maintain complex animation sequences
The navbar animates in from top or bottom based on the current route:
import { useEffect, useRef } from "react";
import { useLocation } from "react-router-dom";
import gsap from "gsap";

function NavbarGlass() {
  const navRef = useRef(null);
  const location = useLocation();

  const isTop = location.pathname.startsWith("/categoria") || 
                location.pathname.startsWith("/producto");

  useEffect(() => {
    const ctx = gsap.context(() => {
      gsap.fromTo(navRef.current,
        { y: isTop ? -40 : 40, opacity: 0 },
        { y: 0, opacity: 1, duration: 1, ease: "power3.out", delay: 0.3 }
      );
    });
    return () => ctx.revert();
  }, [location.pathname]);

  return (
    <header className={`fixed ${isTop ? "top-6" : "bottom-6"}`}>
      <nav ref={navRef}>
        {/* Navigation items */}
      </nav>
    </header>
  );
}

Cart Panel Slide Animation

The shopping cart panel slides in from the right with an overlay fade:
import { useEffect, useRef } from "react";
import gsap from "gsap";

function CartPanel() {
  const { carritoAbierto } = useCart();
  const panelRef = useRef(null);
  const overlayRef = useRef(null);

  useEffect(() => {
    if (carritoAbierto) {
      // Animate in
      gsap.to(panelRef.current, { 
        x: 0, 
        duration: 0.4, 
        ease: "power3.out" 
      });
      gsap.to(overlayRef.current, { 
        opacity: 1, 
        duration: 0.3, 
        pointerEvents: "auto" 
      });
    } else {
      // Animate out
      gsap.to(panelRef.current, { 
        x: "100%", 
        duration: 0.4, 
        ease: "power3.in" 
      });
      gsap.to(overlayRef.current, { 
        opacity: 0, 
        duration: 0.3, 
        pointerEvents: "none" 
      });
    }
  }, [carritoAbierto]);

  return (
    <>
      <div ref={overlayRef} className="fixed inset-0 bg-black/60" />
      <div ref={panelRef} className="fixed top-0 right-0 h-full">
        {/* Cart content */}
      </div>
    </>
  );
}

Stagger Animation

Product cards animate in with a stagger effect:
import { useEffect, useRef } from "react";
import gsap from "gsap";

function Categoria() {
  const gridRef = useRef(null);

  useEffect(() => {
    const ctx = gsap.context(() => {
      if (gridRef.current && gridRef.current.children.length > 0) {
        gsap.fromTo(
          gridRef.current.children,
          { y: 30, opacity: 0 },
          { 
            y: 0, 
            opacity: 1, 
            duration: 0.8, 
            ease: "power3.out", 
            stagger: 0.08,  // 0.08s delay between each item
            delay: 0.3 
          }
        );
      }
    });
    return () => ctx.revert();
  }, [productosFiltrados]);

  return (
    <div ref={gridRef} className="grid grid-cols-3 gap-6">
      {products.map(product => (
        <ProductCard key={product.id} {...product} />
      ))}
    </div>
  );
}

Animation Properties

Common Properties

gsap.to(element, {
  x: 100,        // translateX
  y: -50,        // translateY
  scale: 1.2,    // scale
  rotation: 45,  // rotate in degrees
});

Easing Functions

Music Store primarily uses power3.out and power3.in for natural motion:
// Smooth deceleration (most common)
ease: "power3.out"

// Smooth acceleration (for exit animations)
ease: "power3.in"

// No easing (linear)
ease: "none"

power3.out

Starts fast, ends slow. Best for entrance animations.

power3.in

Starts slow, ends fast. Best for exit animations.

Animation Timing

Timeline Positioning

Control when animations start in a timeline:
const tl = gsap.timeline();

tl.to(el1, { opacity: 1, duration: 1 })
  .to(el2, { opacity: 1 }, "-=0.5")  // Start 0.5s before el1 finishes
  .to(el3, { opacity: 1 }, "+=0.2")  // Start 0.2s after el2 finishes
  .to(el4, { opacity: 1 }, "<")      // Start at the same time as el3

Stagger

Animate multiple elements with a delay between each:
gsap.to(".cards", {
  y: 0,
  opacity: 1,
  duration: 0.8,
  stagger: 0.08  // 0.08s between each card
});

// Advanced stagger
gsap.to(".cards", {
  y: 0,
  opacity: 1,
  stagger: {
    each: 0.1,
    from: "center",
    grid: "auto"
  }
});

Best Practices

1

Always use gsap.context()

Wrap all GSAP animations in gsap.context() for automatic cleanup:
useEffect(() => {
  const ctx = gsap.context(() => {
    // animations here
  });
  return () => ctx.revert();
}, []);
2

Use refs for DOM elements

Store references to elements you want to animate:
const elementRef = useRef(null);

useEffect(() => {
  const ctx = gsap.context(() => {
    gsap.to(elementRef.current, { opacity: 1 });
  });
  return () => ctx.revert();
}, []);

return <div ref={elementRef}>Content</div>;
3

Choose appropriate easing

  • Use power3.out for entrance animations
  • Use power3.in for exit animations
  • Consistent easing creates cohesive motion
4

Optimize performance

  • Animate transform and opacity (GPU-accelerated)
  • Avoid animating layout properties (width, height, padding)
  • Use will-change CSS property sparingly
Performance Tips:
  • x, y, scale, rotation, opacity are performant
  • Avoid animating top, left, margin, padding
  • Use gsap.set() for instant property changes
  • Batch animations when possible

Animation Checklist

Entry Animations

  • Start with opacity: 0
  • Offset position (y: 30)
  • Use fromTo() method
  • Add slight delay

Exit Animations

  • Animate to opacity: 0
  • Move out of view
  • Use power3.in easing
  • Faster duration

Hover Effects

  • Keep duration short (0.3s)
  • Use CSS transitions
  • Reserve GSAP for complex hovers

Page Transitions

  • Coordinate multiple elements
  • Use timeline for sequencing
  • Consider route changes