AnimationsSimpleApril 8, 2026

GSAP Crossfade Transition

Current page fades to opacity 0, new page content swaps in, wrapper fades back to opacity 1. TransitionContext shares a GSAP timeline via React Context so each page can register exit animations.

View Full Demo →

Home

Page 1 of 3

index.jsx
"use client";

import { useEffect, useRef, useState } from "react";
import gsap from "gsap";
import styles from "./styles.module.css";

const PAGES = [
  { label: "Home", bg: "#f5f5f5", color: "#1a1a1a" },
  { label: "About", bg: "#0d0d0d", color: "#ffffff" },
  { label: "Work", bg: "#1a1f2e", color: "#ffffff" },
];

export default function CrossfadeTransition() {
  const [pageIndex, setPageIndex] = useState(0);
  const [displayIndex, setDisplayIndex] = useState(0);
  const wrapperRef = useRef(null);
  const isAnimating = useRef(false);

  function navigate(nextIndex) {
    if (isAnimating.current || nextIndex === pageIndex) return;
    isAnimating.current = true;

    const wrapper = wrapperRef.current;
    gsap
      .to(wrapper, { opacity: 0, duration: 0.3, ease: "power1.in" })
      .then(() => {
        setDisplayIndex(nextIndex);
        setPageIndex(nextIndex);
        return gsap.to(wrapper, { opacity: 1, duration: 0.3, ease: "power1.out" });
      })
      .then(() => {
        isAnimating.current = false;
      });
  }

  const page = PAGES[displayIndex];

  return (
    <div className={styles.demo}>
      <div
        ref={wrapperRef}
        className={styles.page}
        style={{ background: page.bg, color: page.color }}
      >
        <h2 className={styles.pageTitle}>{page.label}</h2>
        <p className={styles.pageHint}>Page {displayIndex + 1} of {PAGES.length}</p>
      </div>
      <div className={styles.nav}>
        {PAGES.map((p, i) => (
          <button
            key={p.label}
            className={`${styles.navBtn} ${i === pageIndex ? styles.active : ""}`}
            onClick={() => navigate(i)}
          >
            {p.label}
          </button>
        ))}
      </div>
    </div>
  );
}
styles.module.css
.demo {
  display: flex;
  flex-direction: column;
}

.page {
  height: 320px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  transition: background 0ms;
}

.pageTitle {
  font-size: 2.5rem;
  font-weight: 700;
  letter-spacing: -0.03em;
}

.pageHint {
  font-size: 0.8125rem;
  opacity: 0.4;
}

.nav {
  display: flex;
  gap: 1px;
  background: #e5e5e5;
  border-top: 1px solid #e5e5e5;
}

.navBtn {
  flex: 1;
  padding: 0.875rem;
  font-size: 0.875rem;
  font-weight: 500;
  font-family: inherit;
  background: #ffffff;
  border: none;
  cursor: pointer;
  color: #6b6b6b;
  transition: background 150ms ease, color 150ms ease;
}

.navBtn:hover {
  background: #f5f5f5;
  color: #1a1a1a;
}

.navBtn.active {
  background: #1a1a1a;
  color: #ffffff;
}
  • gsap

Apr 27, 2026

ANIMATIONS

Cursor Hover Label

A custom cursor label that follows the mouse and fades in when hovering trigger elements. Uses GSAP quickTo for smooth tracking. Trigger elements use data-cursor-label attributes to set label text. Inspired by portfolio/agency sites like Studio PIC.

Apr 27, 2026

ANIMATIONS

Section Transition 01

GSAP ScrollTrigger-based section transition system. Sibling sections opt into parallax, pin, or reveal modes via data attributes. Supports y offset, overlay opacity, and overlay color per section. Mobile strategy simplifies motion on smaller screens.

Apr 27, 2026

ANIMATIONS

Text Reveal 01

GSAP SplitText-based text reveal system. Text elements opt in with data-reveal-01 and split into lines, words, or characters. Supports load-time reveals, scroll-triggered reveals, scrubbed scroll reveals, and manual split-only mode for custom timelines. Per-element overrides for duration, stagger, delay, ease, and replay behavior.