webdevpuneet.com

Three.js Scroll Color Morph — GSAP Distortion Blob on Scroll

Three.js Scroll Color Morph · Scroll · Plain HTML, CSS & JS · Live preview

Copy & paste

Source Code

<section class="cmb-top"><p>Scroll ↓ to morph the shape</p></section>
<section class="cmb-stage" id="cmbStage">
  <canvas id="cmbCanvas"></canvas>
</section>
<section class="cmb-bottom"><p>Form and colour, settled.</p></section>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Vertices displaced along their own normals so the blob inflates as a coherent surface, not a smear
Scroll scales distortion amplitude while a real-time clock keeps the surface churning when paused
Original positions and normals stored once and re-read each frame — no accumulation, exact reset at m=0
computeVertexNormals() each frame so spikes and valleys are lit correctly for a liquid 3D read
Summed-sine pseudo-noise means no external noise library is needed
HSL hue sweep drives material colour, emissive tint, and the CSS stage background from the same value
High-detail icosahedron (48 subdivisions) for a smooth base sphere and fine morphed detail
Pinned, smoothed scrub (0.6), and fully reversible — the blob calms to a clean sphere on scroll-up

About this UI Snippet

How to Build a Scroll-Driven Morphing Color Blob With Three.js and GSAP

Screenshot of the Three.js Scroll Color Morph snippet rendered live

The Three.js Scroll Color Morph snippet takes a smooth sphere and, as the visitor scrolls, churns it into a spiky organic blob while sweeping its colour and the page background through a hue range — the scrollbar controls both the distortion strength and the colour directly, not a timer — using Three.js normal displacement and GSAP's ScrollTrigger plugin, both loaded from a CDN.

Displacement along vertex normals

The morph is pure geometry: a high-detail IcosahedronGeometry (subdivided 48 times for a dense, smooth sphere) has every vertex pushed outward or inward along its own surface normal. Displacing along the normal — rather than in a fixed direction — is what keeps the shape looking like a coherent inflating/deflating surface instead of a smeared mess. The push distance comes from a noise function evaluated at each vertex's original position, so neighboring vertices move by similar amounts and the surface stays continuous.

Scroll controls amplitude, time controls churn

Two inputs drive the look, cleanly separated. The scroll-scrubbed value m (0 to 1) scales how far every vertex is displaced — at 0 the sphere is perfectly smooth, at 1 it's a dramatic spiky blob. Independently, a real-time clock feeds the noise so the surface keeps churning and boiling even when scroll is paused. Scrolling sets *how extreme* the blob is; time keeps it *alive*.

Original positions and normals stored once

Copies of the pristine vertex positions and normals are taken at startup, and every frame the displacement is computed from those originals — never from the previous frame's displaced state. This prevents error accumulation that would otherwise inflate the blob into garbage over time, and it means at m = 0 the geometry is always exactly the original clean sphere, making the morph perfectly reversible.

Normals recomputed each frame for correct lighting

After displacing the vertices, computeVertexNormals() is called so the churning surface is lit correctly frame to frame — spikes catch highlights and valleys fall into shadow, which is what gives the blob its liquid, three-dimensional read. Skipping this step would leave the lighting flat and the morph would look like a texture rather than real geometry.

Colour and background sweep with the morph

The same m value drives an HSL hue sweep applied to the material colour, its emissive tint, and even the CSS background of the stage — so as the blob spikes up it also shifts hue and the whole scene's mood changes together. Tying colour to the identical scrubbed value that drives the geometry keeps form and colour in perfect lockstep and gives the scroll a strong sense of transformation.

scrub: 0.6, pinned, fully reversible

A numeric scrub smooths both the distortion and the colour against noisy input, and because everything derives from the one scrubbed m, scrolling back up returns the blob to a calm smooth sphere in its original colour. This shares its displacement engine with the morphing blob and liquid metal sphere snippets; here scroll is the control. Pair it as a centerpiece with a depth parallax field around it.

Step by step

How to Use

  1. 1
    Load all three CDN scriptsAdd three.min.js, gsap.min.js, and ScrollTrigger.min.js from the CDN panel, in that order.
  2. 2
    Paste HTML, CSS, and JSA smooth glowing sphere appears in a pinned 3D stage.
  3. 3
    Scroll downThe sphere churns into a spiky blob while its colour and the background sweep through a hue range.
  4. 4
    Scroll back upThe blob calms back to a smooth sphere in its original colour, since everything derives from one scrubbed value.
  5. 5
    Tune the distortionChange the noise strength multiplier for a subtler ripple or a wilder, spikier morph.
  6. 6
    Shift the paletteEdit the hue base and range to sweep between any two colours as the shape morphs.

Real-world uses

Common Use Cases

Hero centerpieces
Anchor a landing page with a living blob that transforms and shifts colour as visitors scroll and read.
Generative art sections
Use the churning surface as an evolving generative-art moment tied to scroll depth.
Teaching normal displacement
A readable example of pushing vertices along normals and recomputing normals for correct lighting.
Brand mood transitions
Sweep from a calm cool palette to an energetic warm one to signal a shift in a scroll narrative.
Music and creative studios
A reactive blob suits audio, agency, and studio sites; pair it with a scroll tunnel intro.
Menu and loading backdrops
A morphing organic form makes an atmospheric backdrop for menus and loading screens.

Got questions?

Frequently Asked Questions

Pushing each vertex along its own surface normal keeps the shape reading as one coherent inflating and deflating surface. Displacing in a fixed direction instead would shear the geometry into a smeared mess. Because the push distance comes from noise sampled at each vertex's original position, neighbors move by similar amounts and the surface stays smooth and continuous.

The scroll-scrubbed value m scales how far the vertices are displaced — 0 is a smooth sphere, 1 is a spiky blob. A separate real-time clock feeds the noise so the surface keeps churning even when scrolling is paused. Scroll sets how extreme the blob is; time keeps it alive between scroll movements.

Copies of the pristine vertex positions and normals are taken at startup, and each frame the displacement is computed from those originals, never the previous displaced state. This prevents error from accumulating (which would inflate the blob into garbage) and guarantees that at m = 0 the geometry is exactly the original clean sphere, making the morph perfectly reversible.

After the vertices move, the surface normals are stale, so the lighting would be wrong. Recomputing them each frame makes spikes catch highlights and valleys fall into shadow, giving the blob its liquid, three-dimensional look. Without it the morph would look flat, like a texture rather than real deforming geometry.

Yes. Click JSX for a React component, Vue for a Vue 3 SFC, Angular for a standalone component, or Tailwind for a React + Tailwind version. Build the geometry, store the base arrays, and set up the GSAP timeline inside a mount effect against a canvas ref, and on cleanup kill the ScrollTrigger and call geometry.dispose() and renderer.dispose() so buffers and the WebGL context are freed on unmount.