All components

Clayboan Scroll Animation

GSAP animation component · Published 2026-07-21 · by vanguardia.dev

Open live demo ↗ Raw prompt (.md)

What it does

Full-page scroll gallery with Lenis smooth scroll where each work section is scrubbed by GSAP ScrollTrigger: the image's clip-path morphs from an angular slanted polygon to a full rectangle as the section enters and back to a bottom-slanted polygon as it exits, while SplitText masks each title character and slides them up one-by-one, staggered by scroll position.

How it's built

Categoryscroll
Techgsap, lenis
GSAP pluginsScrollTrigger, SplitText
Complexitypage
Performance costmedium
Mobile-safeyes
Scrollhijacks scrolling

scroll scrub clip-path split-text image-reveal text-reveal lenis gallery editorial

Rebuild it with AI

To reproduce this animation in your own project, copy the prompt below into Claude Code, Cursor or any AI coding agent. The prompt is validated — it describes the exact structure, timing and easing, so the agent rebuilds the effect faithfully and you can then adapt colors, copy and layout to your design.

The full prompt

Clayboan Scroll Gallery — Clip-Path Reveal + Char-by-Char Titles

Goal

Build a full-page vertical scroll gallery (portfolio style) with Lenis smooth scroll where each tall "work" section is scrubbed by GSAP ScrollTrigger. As a section enters the viewport, its full-bleed image's clip-path morphs from an angular slanted polygon into a full rectangle (a diagonal wipe-open reveal); as the section leaves, the clip-path morphs again into a bottom-slanted polygon (a diagonal wipe-close). Meanwhile the big white project title, centered over the image, animates in character by character: SplitText masks every char and each char slides up from below its mask, with each character bound to its OWN scroll window so the reveal cascades left-to-right as you scroll.

Tech

Vanilla HTML/CSS/JS with ES module imports. Use gsap (npm) plus the GSAP plugins ScrollTrigger and SplitText, and lenis for smooth scroll. Register with gsap.registerPlugin(ScrollTrigger, SplitText). Run everything inside a DOMContentLoaded handler.

Layout / HTML

Semantic structure (class names are load-bearing — the JS/CSS query them):

<section class="hero">
  <h1>Beyond the limits</h1>
</section>

<!-- repeat this block 5 times, one per project -->
<section class="work-item">
  <div class="work-item-img"><img src="..." alt="" /></div>
  <div class="work-item-name">
    <h1>Carbon Edge</h1>
  </div>
</section>

<section class="outro">
  <h1>Back to base</h1>
</section>
  • Exactly 5 .work-item sections between the hero and the outro.
  • Project titles, in order: Carbon Edge, Velocity Grid, Aeroform, Mach Horizon, Titan Rail.
  • Hero copy: "Beyond the limits". Outro copy: "Back to base". No nav, no footer — just these 7 stacked sections.

Styling

Font (Google Fonts): Inter Tight (variable weight import). Body: font-family: "Inter Tight", sans-serif; background: #fcfcfc; color: #141414.

Global:

  • * { margin:0; padding:0; box-sizing:border-box }.
  • img { width:100%; height:100%; object-fit:cover }.
  • h1 { text-transform:uppercase; text-align:center; font-size:5rem; font-weight:550; line-height:1 } (weight 550 works because Inter Tight is a variable font).
  • section { position:relative; width:100vw; overflow:hidden }.

Sections:

  • .hero, .outro { height:100svh; display:flex; justify-content:center; align-items:center; padding:2rem } — dark text on the off-white page background.
  • .work-item { height:150svh } — each project section is 1.5 viewport heights tall, which creates the scroll runway for the scrubbed animations.
  • .work-item-img { position:absolute; width:100%; height:100%; will-change:clip-path } with initial clip-path: polygon(25% 25%, 75% 40%, 100% 100%, 0% 100%) (an irregular quadrilateral: top edge slanted downward left-to-right, full bottom). The image fills the whole 150svh section behind the title.
  • .work-item-name { position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); width:100%; z-index:1 } — the title is centered in the section, layered above the image.
  • .work-item-name h1 { color:#fff } (white title over the photo).
  • Media query max-width: 1000px: all h1 (including .work-item-name h1) drop to font-size: 2.5rem.

GSAP effect (be exact)

Lenis + ScrollTrigger wiring
const lenis = new Lenis();
lenis.on("scroll", ScrollTrigger.update);
gsap.ticker.add((time) => { lenis.raf(time * 1000); });
gsap.ticker.lagSmoothing(0);

Lenis drives the scroll; GSAP's ticker drives Lenis (time is in seconds, Lenis wants ms, hence * 1000).

Per work item

Loop gsap.utils.toArray(".work-item"). For each item, grab img = item.querySelector(".work-item-img") and nameH1 = item.querySelector(".work-item-name h1"), then create THREE kinds of scrubbed ScrollTriggers:

1. Title chars — one ScrollTrigger per character.

const split = SplitText.create(nameH1, { type: "chars", mask: "chars" });
gsap.set(split.chars, { y: "125%" });

mask: "chars" wraps every char in its own overflow-clipping mask element, so y: "125%" hides each char fully below its own line box. Then, for each char at index:

ScrollTrigger.create({
  trigger: item,
  start: `top+=${index * 25 - 250} top`,
  end:   `top+=${index * 25 - 100} top`,
  scrub: 1,
  animation: gsap.fromTo(char, { y: "125%" }, { y: "0%", ease: "none" }),
});
  • Each character gets its own 150px scroll window, offset 25px later per index — that per-index offset is what produces the left-to-right cascade (it is a scroll-position stagger, not a time stagger).
  • The negative base offsets (-250 / -100) mean the first characters start revealing BEFORE the section top reaches the viewport top (i.e. while the section is still entering), and scrubbing back down reverses them.
  • scrub: 1 gives the chars a ~1s catch-up smoothing.

2. Image reveal on enter.

ScrollTrigger.create({
  trigger: item,
  start: "top bottom",
  end: "top top",
  scrub: 0.5,
  animation: gsap.fromTo(
    img,
    { clipPath: "polygon(25% 25%, 75% 40%, 100% 100%, 0% 100%)" },
    { clipPath: "polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)", ease: "none" }
  ),
});

While the section travels from "top hits viewport bottom" to "top hits viewport top", the slanted top edge of the polygon unfolds up into the full rectangle — the photo wipes open diagonally. ease: "none" + scrub keeps it perfectly tied to scroll; scrub: 0.5 adds a half-second smoothing lag.

3. Image conceal on exit.

ScrollTrigger.create({
  trigger: item,
  start: "bottom bottom",
  end: "bottom top",
  scrub: 0.5,
  animation: gsap.fromTo(
    img,
    { clipPath: "polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)" },
    { clipPath: "polygon(0% 0%, 100% 0%, 75% 60%, 25% 75%)", ease: "none" }
  ),
});

While the section's bottom travels from viewport bottom to viewport top (the section leaving upward), the BOTTOM edge folds up into a slanted polygon (full top edge kept, bottom collapsing to points at 60%/75% height) — the mirror-image wipe-close of the entrance.

Important: both image triggers animate the same element's clipPath in separate non-overlapping scroll ranges (enter range ends where the section is fully on screen; exit range starts when its bottom reaches the viewport bottom), so they hand off cleanly. All polygons have 4 points so the morph interpolates smoothly. No pinning anywhere — the sections scroll naturally; the 150svh height is what stretches the effect out.

Assets / images

5 full-bleed photographs, one per work item, each filling a 150svh × 100vw section with object-fit: cover — so use large landscape-or-taller images (roughly 4:3 to 3:4 works; they'll be cropped by cover). They should read as a matched editorial set: dramatic, dark, industrial "engineering & speed" photography — think close-ups of aircraft engines, turbine intakes, streamlined land-speed vehicles, high-speed train noses and angular jet fuselages, mostly monochrome/desaturated with hard studio lighting on dark backgrounds. White or near-white machine bodies against black work best under the white titles.

Behavior notes

  • Everything is scroll-driven and fully scrubbed (reversible): scrolling back down plays every reveal backwards. Nothing autoplays on load except that the hero is static text.
  • No pinning, no scroll hijacking beyond Lenis smoothing.
  • Uses 100svh/150svh so mobile browser chrome doesn't cause jumps.
  • Responsive is handled purely by the max-width: 1000px font-size drop; the clip-path and char animations are percentage/offset based and work at any size.
  • Keep will-change: clip-path on .work-item-img — the clip morphs are the heaviest part.