AnimationsSimpleApril 8, 2026
Scale + Slide From Left Transition
Page elements scale up from zero and slide in from the left while fading in. Long 0.25s stagger gives a theatrical, sequential feel. Most dramatic of the GSAP transition patterns.
View Full Demo →Preview
Home
- Featured Work
- Latest Projects
- Studio
Source
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", items: ["Featured Work", "Latest Projects", "Studio"] },
{ label: "Projects", bg: "#0d0d0d", color: "#ffffff", items: ["All Projects", "Case Studies", "Archive"] },
{ label: "About", bg: "#1a1a2e", color: "#ffffff", items: ["Story", "Team", "Process"] },
];
export default function ScaleSlideLeftTransition() {
const [pageIndex, setPageIndex] = useState(0);
const [displayIndex, setDisplayIndex] = useState(0);
const containerRef = useRef(null);
const isAnimating = useRef(false);
function navigate(nextIndex) {
if (isAnimating.current || nextIndex === pageIndex) return;
isAnimating.current = true;
const container = containerRef.current;
gsap.to(container, {
opacity: 0,
duration: 0.3,
ease: "power1.in",
onComplete: () => {
setDisplayIndex(nextIndex);
setPageIndex(nextIndex);
},
});
}
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const items = container.querySelectorAll(`.${styles.item}`);
if (!items.length) return;
// Enter: scale from 0 + slide from left, long 0.25s stagger
gsap.fromTo(
items,
{ opacity: 0, scale: 0, x: -30 },
{
opacity: 1,
scale: 1,
x: 0,
stagger: 0.25,
duration: 0.6,
ease: "power2.out",
onStart: () => gsap.set(container, { opacity: 1 }),
onComplete: () => { isAnimating.current = false; },
}
);
}, [displayIndex]);
const page = PAGES[displayIndex];
return (
<div className={styles.demo}>
<div
ref={containerRef}
className={styles.page}
style={{ background: page.bg, color: page.color }}
>
<h2 className={styles.pageTitle}>{page.label}</h2>
<ul className={styles.list}>
{page.items.map((item) => (
<li key={item} className={styles.item}>{item}</li>
))}
</ul>
</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;
}
.list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 0.5rem;
align-items: center;
}
.item {
font-size: 0.9375rem;
opacity: 0.65;
}
.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;
}
Dependencies
gsap