Three.js Scroll Jellyfish Drift — Bioluminescent Underwater Effect

Three.js Scroll Jellyfish Drift · Scroll · Plain HTML, CSS & JS · Live preview

What's included

Features

Half-sphere bell geometry with cached base vertices displaced by a shared sine pulse — no morph targets
Each tentacle is an independent THREE.Line whose segments undulate with a phase offset by position along its length
Pulse and undulation phase derive from scrubbed scroll progress, not elapsed time, for exact reversibility
MeshPhysicalMaterial with transmission and low opacity gives the bell a convincing translucent look
Additive-blended bioluminescent rim dots and cone-shaped light shafts fake glow with zero postprocessing
Three jellyfish share one construction function but carry independent phase/position offsets to avoid synchronized motion
Camera drifts upward in step with the jellyfish for a continuous ascending point of view
Fully reversible and pinned — scrolling up sinks the jellyfish back down with zero extra code

About this UI Snippet

How to Build a Scroll-Driven Jellyfish Drift With Three.js

Screenshot of the Three.js Scroll Jellyfish Drift snippet rendered live

The Three.js Scroll Jellyfish Drift snippet builds several translucent jellyfish from a displaced half-sphere bell and per-tentacle line geometries, then drifts them upward through deep water with pulsing and undulating motion as the visitor scrolls through a pinned stage — all driven by one scrubbed progress value, no keyframe animation clips.

A half-sphere bell that pulses along its own vertices

Each jellyfish's bell starts as a THREE.SphereGeometry restricted to a partial phi range so it reads as a dome rather than a full ball. Its original vertex positions are cached once into bellBase, and every frame a shared sine pulse factor scales the X/Z of every vertex outward from the bell's own vertical axis — a cheap way to fake the muscular contraction real jellyfish use to swim, without a skeletal rig or morph targets.

Tentacles as independently undulating line geometries

Each tentacle is its own small THREE.BufferGeometry/THREE.Line with a fixed number of segments. Every frame, each segment's horizontal offset is a sine wave whose phase includes a term proportional to along (the segment's position down the tentacle's length) — that term is what makes the wave visibly travel down the tentacle instead of the whole strand swaying as one rigid unit, similar in spirit to the segment-based undulation in fabric ripple but applied per-line rather than per-plane.

Drift and pulse phase both derive from scroll progress

Every jellyfish's vertical position lerps from a deep starting Y to a shallower ending Y by eased scroll progress, and the shared phase value driving both the bell pulse and tentacle sway is computed as t * 10 + phaseOffset — a pure function of the scrubbed progress, not a running clock. That is what keeps the whole scene, pulsing and drifting alike, exactly reversible: scrolling up plays every motion backward instead of continuing to animate forward while the page state moves up.

Light shafts without volumetric lighting

A handful of large, mostly-transparent THREE.ConeGeometry shapes with AdditiveBlending and depthWrite: false fan down from above the scene, faking crepuscular light rays through water with zero postprocessing — the same cheap-glow trick as the halo lines in circuit board trace, applied here as ambient set dressing rather than a focal element.

Independent phase offsets prevent visual synchronization

Each of the three jellyfish gets its own phaseOffset, starting depth, and horizontal base position, so despite sharing identical pulse/undulation code they never pulse or sway in lockstep — a small but important detail for making a handful of instances read as a believable drifting group rather than three copies of one animation.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You do not need to reverse-engineer how jellyfish pulse and drift on scroll without a rig or animation clips. Paste this snippet's HTML, CSS, and JS into an AI assistant like Claude and ask it to explain why the bell pulse scales vertices outward from the vertical axis rather than moving them individually, or how the phase-offset-by-segment-position term creates a traveling tentacle wave. The same assistant can help you extend it — ask it to add small fish that dart away from the jellyfish as they pass, vary bell color with a subtle hue shift synced to the pulse, or add a caustic light-pattern texture on the light shafts. It can also help optimize further, for instance batching all tentacle lines from all jellyfish into fewer draw calls. 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 jellyfish drift" scene 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 it and updated on window resize including aspect ratio, plus THREE.Fog in a deep ocean blue and a few large, mostly-transparent additively-blended cone shapes fanning down from above to fake light shafts.
- Write a function that constructs one jellyfish: a half-sphere "bell" geometry (a partial-phi THREE.SphereGeometry) whose original vertex positions are cached once, a ring of small additively-blended accent points around its rim, and several individual THREE.Line "tentacles" each with their own small BufferGeometry of a fixed segment count.
- Every animation frame, compute a shared phase value as a pure function of the current scrubbed scroll progress (not elapsed time) plus a per-jellyfish phase offset, and use a shared sine value derived from that phase to scale every bell vertex's horizontal (X/Z) coordinates outward from its cached base position, simulating a pulsing contraction.
- For each tentacle, recompute every segment's horizontal offset each frame as a sine wave whose phase includes a term proportional to that segment's position along the tentacle's length, so the wave visibly travels down the tentacle rather than the whole strand swaying rigidly as one piece.
- Interpolate each jellyfish's vertical position from a deep starting Y to a shallower ending Y using eased scroll progress, and give each of several jellyfish instances its own horizontal base position and phase offset so they do not pulse or drift in lockstep.
- 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 that drives all of the above.
- Confirm scrolling back up sinks the jellyfish back down and reverses their pulse/tentacle phase exactly, since every visual property is a pure function of the current scrubbed progress value.

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="jel-stage" id="jelStage">
  <div class="jel-intro-overlay"><p>Scroll ↓ to drift jellyfish upward through deep water</p></div>
  <canvas id="jelCanvas"></canvas>
  <div class="jel-hud"><span id="jelPct">0</span>% ascended</div>
</section>
<section class="jel-bottom"><p>Bioluminescent drifters, nearing the light.</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 JSThree jellyfish appear deep in dark water inside a pinned 3D stage with a live "% ascended" read-out.
  3. 3
    Scroll downThe jellyfish pulse, undulate their tentacles, and drift upward together toward the light shafts above.
  4. 4
    Scroll back upThey sink back down and their pulse/sway phase reverses exactly, since it is a pure function of scroll progress.
  5. 5
    Add or restyle jellyfishCall makeJellyfish(hue, scale) again with a new color and size, then push a matching jellyConfig entry.
  6. 6
    Adjust the pacingChange the ScrollTrigger end value (+=420%) for a slower or faster ascent.

Real-world uses

Common Use Cases

Aquarium and marine-life sites
Open an aquarium, oceanarium, or marine-biology homepage with jellyfish that visibly drift as visitors scroll past the hero.
Ambient and meditation apps
The slow pulsing, glowing drift suits calm, ambient brand moments better than a static hero image.
Generative art and creative-coding portfolios
Showcase procedural bell/tentacle animation work with a piece that visibly demonstrates the underlying vertex math.
Underwater or deep-sea game landing pages
Pair with coral reef grow or ink drop diffusion for a layered underwater scroll story.
Teaching vertex-based procedural animation
A compact real-world example of sine-driven vertex displacement for organic motion without a rig or keyframes.
Scroll-story chapter breaks
Use the ascent as a mid-page transition between sections, similar to galaxy formation.

Got questions?

Frequently Asked Questions

The bell's original vertex positions are cached once. Every frame, a single shared sine value scales each vertex's X and Z outward from the bell's vertical axis while leaving Y untouched, producing a breathing contraction-and-release motion with a couple of lines of code instead of a morph-target animation clip.

Each tentacle segment's sway includes a phase term proportional to its position along the tentacle (the "along" variable). Because segments further down the tentacle are offset further in phase, they reach their peak sway at a slightly later point in the shared sine cycle, which reads as a wave traveling downward rather than uniform rigid swaying.

Deriving phase as t * 10 + phaseOffset from the scrubbed scroll progress value means the pulse and tentacle sway at any given scroll position are always identical, whether arrived at by scrolling down or back up. If phase instead accumulated with elapsed time, scrolling backward would not visually reverse the pulsing and swaying.

All three jellyfish share the exact same makeJellyfish() construction and update logic. Without a distinct phaseOffset, baseX, and startY per instance, they would pulse, sway, and drift in perfect lockstep, which reads as an obviously repeated copy rather than a believable small group drifting independently.

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 jellyfish group and geometry caches inside a mount effect keyed to a canvas ref, and on unmount kill the ScrollTrigger instance, dispose every geometry and material, and call renderer.dispose().