More Scroll Snippets
Scroll Shape Morph — Free GSAP Blob Morph Snippet
Scroll Shape Morph · Scroll · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Scroll Shape Morph — Melt a Blob Into Geometry With the Scrollbar

The scroll shape morph pins a single gradient element and reshapes it as you scroll: an organic blob tightens into a soft pebble, then squares off into crisp geometry — while its hue rotates and captions narrate each state. Scrolling up melts it back. It's a lightweight way to visualize a "flexible to structured" story without SVG path morphing. This snippet builds it with GSAP ScrollTrigger (from a CDN) and interpolated 8-value border-radius.
Blobs are just asymmetric border-radius
The organic shape comes from border-radius's full syntax: 58% 42% 63% 37% / 45% 58% 42% 55% sets each corner's horizontal radii before the slash and vertical radii after. Unequal values per corner turn a square div into a lumpy, organic form — no SVG, no clip-path, no mask. The gradient fill and a colored shadow complete the blob look on a single div.
GSAP interpolates all eight radii between states
Each morph tweens borderRadius from one 8-value string to the next. GSAP parses complex CSS values into their numeric components and interpolates each of the eight percentages independently, so every corner travels smoothly from its blob value to its square value. The one rule: all states must use the same full syntax — mixing 12% shorthand with 8-value strings would misalign the component mapping, which is why the square state is written out as all eight values.
Rotation disguises the corner correspondence
A pure radius morph can look like corners "breathing" in place. Adding rotation: 40° then 90° across the two morphs keeps the silhouette turning while it reshapes, so the eye reads continuous transformation rather than four corners independently inflating. By the square state the element has turned a quarter — arriving at geometry squared-up, not tilted.
hue-rotate recolors without a second gradient
The fill appears to shift from indigo-cyan toward warmer tones via filter: hue-rotate() — one interpolatable value — instead of crossfading stacked gradient layers. It's the cheapest way to make each shape state feel like its own "chapter" color while the underlying gradient never changes.
Captions narrate state boundaries
Three captions hand off at the morph boundaries on the same scrubbed timeline (out at 0.6/1.6, in at 0.75/1.75), with a small gap between exit and entrance so two captions never overlap mid-scrub. As always with single-timeline choreography, the narration can't drift from the shape it describes.
Two morphs, 220% of scroll
Each state transition owns one timeline unit across a +=220% pin — roughly a full viewport-height of scrolling per morph, slow enough to watch corners travel. scrub: 0.5 smooths the reshaping into a melt rather than a stutter.
Where this approach ends
Radius morphing handles convex, roundish forms. For star shapes, letters, or concave silhouettes you'd graduate to SVG path morphing (MorphSVG or flubber); for a continuously wobbling blob see the liquid blob snippet.
Box-shadow and gradient stay constant while the silhouette changes
The blob's colored shadow and gradient fill never animate — only border-radius, rotation, and the hue-rotate filter move. Keeping the shadow static means the browser doesn't need to recompute blur geometry every frame; it just repaints the same shadow shape clipped by whatever the border-radius currently is. That division of labor — shape-defining properties animate, decorative properties stay fixed — is what keeps a filter-heavy element (blur shadow, hue rotation) affordable under a scrub running at 60fps.
Customizing it
Add states to the SHAPES array (one tween per transition), retint via the hue-rotate degrees, or put a logo inside the shape. Pair it with scroll color sections for page-wide theming, a scroll word wheel for the headline, or wave divider transitions between sections.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You don't have to work out the 8-value border-radius interpolation on your own. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why every entry in the SHAPES array must use the full eight-value border-radius syntax rather than shorthand, or why the rotation is layered on top of the radius morph specifically to disguise "corner breathing." The same assistant can help optimize it — asking whether the hue-rotate filter and the border-radius interpolation together risk any repaint cost at 60fps under scrub, or whether the caption handoff gaps (0.6 to 0.75, 1.6 to 1.75) are wide enough to avoid overlap on slower devices. It's also useful for extending the effect: ask it to add a fourth shape state, swap the rounded-square end state for an entirely different silhouette, or make the hue-rotate degrees driven by a data attribute per state instead of a fixed value per tween. Treat the code less like a finished artifact and more like a starting point for a conversation.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build a "scroll shape morph" effect in plain HTML, CSS, and JavaScript using GSAP with its ScrollTrigger plugin (load both from a CDN) — using only CSS border-radius morphing, no SVG path morphing, no clip-path.
Requirements:
- A single gradient-filled div acting as the shape, plus several caption elements describing each state, all inside a pinned stage section.
- Define at least three shape states as strings using the full eight-value border-radius syntax (four horizontal radii before a slash, four vertical radii after), ranging from a strongly asymmetric organic blob shape to a nearly-square rounded rectangle — every state must use the same eight-value format so GSAP can interpolate each corner's value independently and consistently between states.
- Register one GSAP timeline on a single ScrollTrigger with pin: true and a scrub smoothing value, over a scroll distance long enough to give each shape transition its own clearly perceivable timeline segment.
- Tween the shape's border-radius from state to state across the timeline, and on each of those same tweens simultaneously animate its rotation (increasing across each transition) and a CSS filter hue-rotate value (also increasing across each transition), so the shape visibly turns and recolors while its silhouette changes — the rotation must be layered on top of the radius change so the shape reads as one continuous transformation, not a static shape whose corners just move independently.
- On the same shared timeline, fade and vertically slide each caption in and out at explicit positions synced to the shape's state boundaries, leaving a small time gap between one caption's exit and the next caption's entrance so two captions can never be visible or animating at once.
- Do not animate the shape's box-shadow or gradient background — only the border-radius, rotation, and hue-rotate filter should be part of the tweened properties, so the browser can repaint the same static shadow/gradient shape clipped to whatever radius is currently active rather than recomputing shadow geometry every frame.
- Confirm scrolling back up smoothly un-squares the shape back into the organic blob, purely because the timeline is scrubbed in reverse.Step by step
How to Use
- 1Add the GSAP CDNsInclude gsap and ScrollTrigger from the CDN panel.
- 2Paste HTML, CSS, and JSA gradient blob renders with its first caption.
- 3Scroll into the stageThe blob starts tightening and turning.
- 4Pass each stateCaptions hand off as pebble becomes square.
- 5Scroll back upThe square melts back into the blob.
- 6Add shape statesAppend 8-value strings to the SHAPES array.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Through border-radius's full syntax: four horizontal radii before the slash and four vertical after, one pair per corner. Unequal percentages like 58% 42% 63% 37% / 45% 58% 42% 55% pull each corner differently, producing a lumpy organic silhouette from a single div — no SVG, clip-path, or mask involved.
GSAP parses complex CSS values into numeric components and interpolates each independently, so tweening borderRadius between two 8-value strings animates all eight percentages in parallel. The requirement is consistent syntax across states — the square is written as all eight 12% values rather than shorthand so components stay aligned.
A pure radius morph reads as corners inflating and deflating in place. Layering rotation (0° → 40° → 90°) keeps the silhouette turning during the reshape, which the eye reads as one continuous transformation. The quarter-turn total also means the element arrives at its square state squared-up rather than tilted.
No — border-radius can only produce convex, roundish forms since it just rounds a rectangle's corners. Concave shapes, stars, or glyphs need SVG path interpolation (GSAP's MorphSVG plugin or flubber) where you tween between path definitions with matched point counts. For roundish state stories, though, radius morphing is far lighter.
Because only shape-defining properties — border-radius, rotation, and the hue-rotate filter — are tweened, while the box-shadow and gradient fill stay fixed. The browser repaints the same shadow geometry clipped to whatever radius is current, rather than recomputing blur math every frame. That separation of "what animates" from "what decorates" is what keeps a filter-heavy element affordable under a 60fps scrub.
Create the timeline in a mount effect — useEffect, onMounted, or ngAfterViewInit — inside gsap.context scoped to the stage ref, and revert it on cleanup so the pin unregisters on unmount. Keep SHAPES as a constant outside the component to avoid re-allocations, and leave borderRadius writes to GSAP rather than framework state. The blob's gradient and sizing express naturally in Tailwind arbitrary values.