Three.js Scroll Liquid Metal Blob — Chrome Noise Displacement

Three.js Scroll Liquid Metal Blob · Scroll · Plain HTML, CSS & JS · Live preview

What's included

Features

High-subdivision THREE.IcosahedronGeometry gives near-uniform vertex spacing for even displacement across the whole surface
Custom sum-of-sines noise3() function displaces vertices with zero external noise library dependency
Displacement amplitude is directly proportional to smoothstepped scroll progress, guaranteeing full reversibility
Base rest positions are cached once in a Float32Array so every frame computes displacement from a stable reference
Time-varying phase offset keeps the ripple pattern alive at rest without affecting displacement amplitude
MeshStandardMaterial with metalness 1 / low roughness plus three colored point lights approximate a chrome environment reflection
geometry.computeVertexNormals() runs each frame so lighting stays correct as the surface deforms
Fully reversible pinned scroll animation — scrolling up relaxes the blob back to a perfect sphere

About this UI Snippet

How to Build a Scroll-Driven Liquid Metal Blob With Three.js

Screenshot of the Three.js Scroll Liquid Metal Blob snippet rendered live

The Three.js Scroll Liquid Metal Blob snippet starts as a perfectly smooth chrome sphere and, as the visitor scrolls through a pinned stage, displaces every vertex outward along its own normal by a noise value whose amplitude ramps with scroll progress — morphing a static icosahedron into an organic, rippling liquid-metal form entirely on the CPU, no fragment shader required.

High-subdivision icosahedron as the base mesh

A THREE.IcosahedronGeometry(radius, 4) gives a near-uniform triangulated sphere with a couple thousand evenly spaced vertices — far more uniform than a lat/long SphereGeometry, which bunches vertices at the poles and would make displacement look uneven there, while staying light enough to update and recompute normals for every frame. Every vertex's original position is copied once into a flat basePos Float32Array before any displacement runs, so the "rest shape" is always available to compute a fresh offset from, frame after frame.

Sum-of-sines noise instead of a Perlin/Simplex library

Rather than pulling in a full noise library, noise3(x, y, z) combines three offset sine/cosine products at different frequencies and phases into one continuous, deterministic pseudo-noise field. It is not a mathematically rigorous gradient noise, but it is smooth, has no visible tiling at the scales used here, and costs only a handful of trig calls — cheap enough to evaluate for every vertex, every frame, at 60fps — a "good enough, cheap enough" philosophy applied along vertex normals instead of a height grid.

Displacement along the per-vertex normal, scaled by progress

Each frame, a vertex's live position is recomputed as normalize(basePos) * radius * (1 + noise * amplitude), where amplitude is directly proportional to the smoothstepped scroll progress. At eased = 0, amplitude is zero and every vertex sits exactly on the original sphere; as eased climbs, the same noise field is scaled up, pushing vertices further from their rest position and reading as the surface "melting" into liquid ripples. Because displacement is always a function of the current eased value rather than an accumulated delta, scrolling back up shrinks the amplitude back toward zero and the surface relaxes back to a perfect sphere with no residual distortion.

Time-varying phase keeps the surface alive without breaking reversibility

The noise field's input coordinates are also offset by a slowly accumulating clock value, so the ripple pattern continues to drift and shimmer even while scroll is paused — a small but important detail for a convincing "liquid" material. This phase drift never affects the *amplitude*, only *which* ripple pattern is currently visible, so it never prevents the blob from returning to a smooth sphere when eased reaches zero; it only changes what the ripples look like the next time amplitude rises again.

Metalness, roughness, and colored point lights stand in for an environment map

MeshStandardMaterial with metalness: 1 and roughness: 0.16 gives the blob a chrome-like physically based response, and three colored point lights (cool cyan, blue, and a bright white key) positioned around the mesh substitute for a full HDRI environment map, producing convincing specular highlights and reflective-looking gradients across the rippling surface without the cost of loading or generating a cube/PMREM environment texture.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You do not need to reverse-engineer how a chrome sphere ripples into liquid metal without a custom shader. Paste this snippet's HTML, CSS, and JS into an AI assistant like Claude and ask it to explain why base positions are cached separately from live positions, or how the sum-of-sines noise3 function produces a smooth deformation field. The same assistant can help you extend it — ask it to port the displacement into a GLSL vertex shader for better performance at higher resolutions, add a real PMREM-generated environment map for sharper reflections, or vary the noise frequency by scroll velocity for a more reactive feel. Treat the code as a conversation starter, not a finished artifact.

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:

text
Build a "scroll-scrubbed liquid metal blob" in plain HTML, CSS, and JavaScript using Three.js, GSAP, and GSAP's ScrollTrigger plugin, all loaded from a CDN (no bundler, no build step).

Requirements:
- A pinned section containing a full-size canvas, with a WebGLRenderer and PerspectiveCamera sized to the canvas element (not window.innerWidth/innerHeight) and updated on window resize including aspect ratio.
- Create a high-subdivision THREE.IcosahedronGeometry sphere and cache every vertex's original position once into a flat Float32Array before any displacement is applied.
- Implement a lightweight custom 3D noise function using a small combination of sine and cosine terms at different frequencies and phases (no external noise library) that takes x, y, z and returns a smooth pseudo-random value.
- Give the mesh a MeshStandardMaterial with metalness near 1 and low roughness, lit by two or three colored point lights positioned around it to simulate a chrome environment reflection without loading an actual environment map.
- Register a GSAP tween on a ScrollTrigger targeting the pinned section, with pin: true, start at top top, a numeric scrub, and a multi-hundred-percent end, animating a single plain progress value from 0 to 1 with linear easing.
- Every animation frame, apply smoothstep easing to the scrubbed progress to get a displacement amplitude, then for every vertex compute its normalized direction from the cached base position, evaluate the noise function at that vertex's base coordinates offset by a slowly accumulating clock value, and set the vertex's live position to its normalized direction times the base radius times one plus noise times amplitude.
- After updating all vertex positions in a frame, set the position attribute's needsUpdate flag and call computeVertexNormals so lighting stays correct as the surface deforms.
- Slowly rotate the blob and let one of the lights drift for added visual interest, and dolly the camera slightly closer as the effect intensifies.
- Confirm scrolling back up reverses the effect smoothly, relaxing the surface back to a perfectly smooth sphere, since displacement amplitude is a direct, non-accumulating function of scroll progress.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Source Code

<section class="lqm-stage" id="lqmStage">
  <div class="lqm-intro-overlay"><p>Scroll ↓ to melt the sphere into rippling liquid metal</p></div>
  <canvas id="lqmCanvas"></canvas>
  <div class="lqm-hud"><span id="lqmPct">0</span>% molten</div>
</section>
<section class="lqm-bottom"><p>A fully rippling liquid-metal form.</p></section>

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 chrome sphere appears inside a pinned 3D stage with a live "% molten" read-out.
  3. 3
    Scroll downThe sphere's surface ripples outward into an organic liquid-metal blob as noise amplitude ramps up.
  4. 4
    Scroll back upThe surface relaxes back to a perfectly smooth sphere exactly in reverse, since amplitude is tied directly to progress.
  5. 5
    Retune the lookChange the IcosahedronGeometry detail level for more or fewer vertices, or the noise3 frequencies for finer or coarser ripples.
  6. 6
    Adjust the pacingChange the ScrollTrigger end value (+=450%) for a slower or snappier melt relative to scroll distance.

Real-world uses

Common Use Cases

Product and brand hero sections
A chrome blob that ripples into life suits automotive, tech, or luxury product landing pages.
Generative and shader-adjacent art portfolios
Demonstrate CPU-side vertex displacement technique with a piece that visibly ripples on scroll.
Music and audio brand sites
Pair a liquid metal surface with an album or single release for a tactile, futuristic visual hook.
Loading and transition screens
Use the molten ramp-up as a scroll-triggered transition between two page sections.
Teaching vertex displacement without shaders
A compact, readable example of per-vertex noise displacement entirely on the CPU for teaching purposes.
Sci-fi or liquid-metal character reveals
Similar surface logic could seed a T-1000-style character intro or boss reveal moment.

Got questions?

Frequently Asked Questions

A true gradient noise implementation is more mathematically well-behaved, but for a decorative surface ripple, three offset sine/cosine products at different frequencies produce a smooth, non-tiling-looking field that is visually convincing at the scale used here, with far less code and no external dependency to load.

If displacement were applied to the geometry's current (already displaced) position every frame, the offsets would compound and the mesh would drift away from a sphere permanently, with no way to reverse it. By always computing the new position from the original, cached basePos array, displacement amplitude alone controls how far from the rest shape the surface sits, which is what makes shrinking amplitude back to zero return the mesh exactly to a sphere.

For a few tens of thousands of vertices, writing floats into a typed array and calling setXYZ is cheap relative to a 16ms frame budget, and it avoids the complexity of a custom vertex shader. For much higher vertex counts, moving the same noise3 formula into a ShaderMaterial vertex shader would move the work to the GPU and scale further, at the cost of losing easy CPU-side inspection of positions.

MeshStandardMaterial lighting depends on accurate surface normals. Since vertex positions change every frame, their normals would go stale (still describing the smooth sphere) without recomputation, producing visibly wrong shading on the rippled surface. Recomputing normals after every position update keeps the metallic highlights following the actual deformed surface.

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, base position cache, and GSAP ScrollTrigger inside a mount effect keyed to a canvas ref, and on unmount kill the ScrollTrigger instance, dispose geometry/material, and call renderer.dispose().