Skip to main content

Overview

Music Store uses GSAP (GreenSock Animation Platform) to create smooth, professional animations. Animations enhance the user experience with fade-ins, slide-ins, and staggered reveals.

Installation

npm install gsap

Basic Usage Pattern

All animations follow a consistent pattern using GSAP context and React’s useEffect:
import { useEffect, useRef } from "react";
import gsap from "gsap";

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

  useEffect(() => {
    const ctx = gsap.context(() => {
      // Animations here
    });

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

  return <div ref={elementRef}>Content</div>;
}
GSAP context (gsap.context()) is used for automatic cleanup and scoping. The cleanup function ctx.revert() is called when the component unmounts.

Hero Section Animations

The Hero component uses a timeline to orchestrate multiple animations:
src/components/Hero/Hero.jsx
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"
        )
        .fromTo(btnRef.current,
          { y: 20, opacity: 0 },
          { y: 0, opacity: 1, duration: 1 },
          "-=0.8"
        );
    });

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

  return (
    <section>
      <div ref={titleRef}>
        <h1>Music Store</h1>
      </div>
      <div ref={textRef}>
        <p>ENCUENTRA TU SONIDO IDEAL CON NOSOTROS</p>
      </div>
      <div ref={btnRef}>
        <button>Mirar Tienda</button>
      </div>
    </section>
  );
}

Timeline Breakdown

Title Animation

Starts from y: 50, opacity: 0 and animates to visible position over 1.5s

Text Animation

Begins 1 second before previous animation ends (-=1), creating overlap

Button Animation

Starts 0.8s before text animation completes for smooth flow

Timeline Position Parameter

The third parameter in fromTo() controls timing:
.fromTo(element, from, to, "-=1")  // Start 1s before previous animation ends
.fromTo(element, from, to, "+=0.5") // Start 0.5s after previous animation ends
.fromTo(element, from, to)          // Start after previous animation completes

Category Page Animations

The Categoria component animates the sidebar and product grid:
src/pages/Categoria.jsx
import { useEffect, useRef } from "react";
import gsap from "gsap";

function Categoria({ productos }) {
  const asideRef = useRef(null);
  const gridRef = useRef(null);

  useEffect(() => {
    const ctx = gsap.context(() => {
      gsap.fromTo(asideRef.current,
        { x: -30, opacity: 0 },
        { x: 0, opacity: 1, duration: 1, ease: "power3.out", delay: 0.2 }
      );

      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, delay: 0.3 }
        );
      }
    });
    return () => ctx.revert();
  }, [nombre, productosFiltrados]);

  return (
    <main>
      <aside ref={asideRef}>
        {/* Filter sidebar */}
      </aside>
      <div ref={gridRef}>
        {productosFiltrados.map(prod => (
          <ProductCard key={prod.id} {...prod} />
        ))}
      </div>
    </main>
  );
}

Stagger Animation

The stagger property creates a cascading effect:
gsap.fromTo(
  gridRef.current.children,  // Animates all child elements
  { y: 30, opacity: 0 },
  { 
    y: 0, 
    opacity: 1, 
    duration: 0.8, 
    stagger: 0.08  // 0.08s delay between each child
  }
);
  • 0.08: Each product card starts animating 0.08 seconds after the previous one
  • Creates a smooth wave effect across the grid
  • Works on any collection of elements (children, querySelectorAll results, etc.)

Product Detail Animations

The product detail page animates three sections with overlapping timing:
src/components/productos/ProductosDetalle.jsx
function ProductoDetalle({ productos }) {
  const imgRef = useRef(null);
  const infoRef = useRef(null);
  const descRef = useRef(null);

  useEffect(() => {
    if (!producto) return;

    const ctx = gsap.context(() => {
      gsap.timeline({ defaults: { ease: "power3.out" } })
        .fromTo(imgRef.current,
          { x: -40, opacity: 0 },
          { x: 0, opacity: 1, duration: 1.2 }
        )
        .fromTo(infoRef.current,
          { x: 40, opacity: 0 },
          { x: 0, opacity: 1, duration: 1.2 },
          "-=1"
        )
        .fromTo(descRef.current,
          { y: 30, opacity: 0 },
          { y: 0, opacity: 1, duration: 1 },
          "-=0.6"
        );
    });

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

  return (
    <div>
      <div ref={imgRef}>
        <img src={producto.imagen} />
      </div>
      <div ref={infoRef}>
        <h1>{producto.nombre}</h1>
        <p>${producto.precio}</p>
      </div>
      <div ref={descRef}>
        <p>{producto.descripcion}</p>
      </div>
    </div>
  );
}

Animation Flow

  1. Image slides in from left (x: -40)
  2. Info slides in from right (x: 40) starting 1s before image completes
  3. Description fades up from below (y: 30) starting 0.6s before info completes

Common Animation Properties

fromTo() Method

gsap.fromTo(element, fromVars, toVars, position)

fromVars

Starting state: { x: -30, opacity: 0 }

toVars

Ending state: { x: 0, opacity: 1, duration: 1 }

position

Timeline position: "-=1", "+=0.5", or omit

ease

Easing function: "power3.out", "elastic", "bounce", etc.

Common Easing Functions

// Smooth deceleration (most common)
{ ease: "power3.out" }

// Smooth acceleration
{ ease: "power3.in" }

// Smooth acceleration and deceleration
{ ease: "power3.inOut" }

// Bouncy effect
{ ease: "bounce.out" }

// Elastic spring effect
{ ease: "elastic.out(1, 0.3)" }

Re-animation on Changes

Animations can re-trigger when dependencies change:
src/pages/Categoria.jsx
useEffect(() => {
  const ctx = gsap.context(() => {
    // Animations
  });
  return () => ctx.revert();
}, [nombre, productosFiltrados]); // Re-run when these change
When nombre (category) or productosFiltrados changes, the cleanup function runs first (reverting animations), then new animations are applied.

Best Practices

Always Use Refs

// ✅ Correct - using ref
const elementRef = useRef(null);
gsap.fromTo(elementRef.current, { ... }, { ... });

// ❌ Wrong - direct DOM query
gsap.fromTo(".my-class", { ... }, { ... });

Clean Up with Context

// ✅ Correct - automatic cleanup
const ctx = gsap.context(() => {
  gsap.fromTo(...);
});
return () => ctx.revert();

// ❌ Wrong - no cleanup
gsap.fromTo(...);

Check Element Existence

// ✅ Correct - check before animating
if (gridRef.current && gridRef.current.children.length > 0) {
  gsap.fromTo(gridRef.current.children, ...);
}

// ✅ Correct - early return
if (!producto) return;
gsap.fromTo(...);

Performance Tips

Use Transforms

Prefer x, y, scale over left, top, width for better performance

Batch Animations

Use timelines and stagger instead of individual animations

Set Defaults

Use timeline defaults for repeated properties: { defaults: { ease: "power3.out" } }

Revert on Unmount

Always clean up with ctx.revert() to prevent memory leaks

Build docs developers (and LLMs) love