AnimationsSimpleApril 8, 2026

Heading Scroll Parallax

Oversized display headings drift vertically at different rates as you scroll, creating layered depth. Each heading moves independently using useTransform with unique y ranges.

View Full Demo →

Motion

Design

Studio

demo.jsx
import HeadingScrollParallax from "./index.jsx";
import styles from "./demo.module.css";

export default function HeadingScrollParallaxDemo() {
  return (
    <div>
      {/* Intro — pushes the parallax section below the fold */}
      <section className={styles.intro}>
        <p className={styles.introLabel}>Scroll animation</p>
        <h2 className={styles.introTitle}>Each heading moves at a different speed as you scroll through the section.</h2>
      </section>

      {/* The component — scroll into this to see the parallax */}
      <HeadingScrollParallax headings={["Motion", "Design", "Studio"]} />

      {/* Trailing space — lets you scroll past the section */}
      <section className={styles.outro}>
        <p className={styles.outroText}>Keep scrolling to see the full effect.</p>
      </section>
    </div>
  );
}
index.jsx
"use client";

import { useRef } from "react";
import { useScroll, useTransform, motion } from "framer-motion";
import styles from "./styles.module.css";

export default function HeadingScrollParallax({ headings = [] }) {
  const ref = useRef(null);

  const { scrollYProgress } = useScroll({
    target: ref,
    offset: ["start end", "end start"],
  });

  const y0 = useTransform(scrollYProgress, [0, 1], ["0vh", "-20vh"]);
  const y1 = useTransform(scrollYProgress, [0, 1], ["0vh", "-10vh"]);
  const y2 = useTransform(scrollYProgress, [0, 1], ["0vh", "-30vh"]);

  const yValues = [y0, y1, y2];

  return (
    <section ref={ref} className={styles.section}>
      <div className={styles.headings}>
        {headings.map((heading, i) => (
          <motion.h2
            key={heading}
            className={styles.heading}
            style={{ y: yValues[i % 3], willChange: "transform" }}
          >
            {heading}
          </motion.h2>
        ))}
      </div>
    </section>
  );
}
demo.module.css
.intro {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 3rem;
  background: #f5f5f0;
}

.introLabel {
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #9b9b9b;
  margin-bottom: 1.25rem;
}

.introTitle {
  font-size: clamp(1.5rem, 3vw, 2.5rem);
  font-weight: 600;
  letter-spacing: -0.03em;
  color: #1a1a1a;
  max-width: 640px;
  line-height: 1.2;
}

.outro {
  min-height: 50vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #0d0d0d;
}

.outroText {
  font-size: 0.875rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.25);
}
styles.module.css
.section {
  position: relative;
  min-height: 60vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 4rem 2rem;
  background: #0d0d0d;
  overflow: hidden;
}

.headings {
  display: flex;
  flex-direction: column;
  gap: 0;
  align-items: center;
}

.heading {
  font-size: clamp(3rem, 10vw, 8rem);
  font-weight: 700;
  letter-spacing: -0.04em;
  color: #ffffff;
  line-height: 0.9;
  margin: 0;
  white-space: nowrap;
}

.heading:nth-child(even) {
  color: transparent;
  -webkit-text-stroke: 1px rgba(255, 255, 255, 0.3);
}
  • framer-motion

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.